Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 10, 22:30 UTC
| 1 | // Copyright 2015 Matthew Holt and The Caddy Authors |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | |
| 15 | package caddy |
| 16 | |
| 17 | import ( |
| 18 | "context" |
| 19 | "fmt" |
| 20 | "os" |
| 21 | "os/exec" |
| 22 | "strings" |
| 23 | "testing" |
| 24 | "time" |
| 25 | |
| 26 | "github.com/caddyserver/caddy/v2/notify" |
| 27 | "golang.org/x/sys/windows/svc" |
| 28 | ) |
| 29 | |
| 30 | func TestRunnerStatusOrdering(t *testing.T) { |
| 31 | for _, order := range []string{"ready first", "runner first", "concurrent"} { |
| 32 | t.Run(order, func(t *testing.T) { |
| 33 | notify.SetGlobalStatus(nil) |
| 34 | t.Cleanup(func() { notify.SetGlobalStatus(nil) }) |
| 35 | requests := make(chan svc.ChangeRequest) |
| 36 | statuses := make(chan svc.Status, 4) |
| 37 | done := make(chan struct{}) |
| 38 | if order == "ready first" { |
| 39 | _ = notify.Ready() |
| 40 | } |
| 41 | go func() { |
| 42 | defer close(done) |
| 43 | runner{}.Execute(nil, requests, statuses) |
| 44 | }() |
| 45 | if order == "runner first" { |
| 46 | select { |
| 47 | case s := <-statuses: |
| 48 | if s.State != svc.StartPending { |
| 49 | t.Fatalf("initial status: %+v", s) |
| 50 | } |
| 51 | case <-time.After(5 * time.Second): |
| 52 | t.Fatal("runner did not start") |
| 53 | } |
| 54 | } |
| 55 | requestDone := make(chan struct{}) |
| 56 | go func() { |
| 57 | defer close(requestDone) |
| 58 | defer close(requests) |
| 59 | if order != "ready first" { |
| 60 | _ = notify.Ready() |
| 61 | } |
| 62 | // The snapshot is deliberately stale: the owner must publish RUNNING. |
| 63 | requests <- svc.ChangeRequest{Cmd: svc.Interrogate, CurrentStatus: svc.Status{State: svc.StartPending}} |
| 64 | }() |
| 65 | t.Cleanup(func() { |
| 66 | select { |
| 67 | case <-done: |
| 68 | case <-time.After(5 * time.Second): |
| 69 | t.Error("runner did not return") |
| 70 | } |
| 71 | }) |
| 72 | select { |
| 73 | case <-requestDone: |
| 74 | case <-time.After(5 * time.Second): |
| 75 | t.Fatal("notification or Interrogate blocked") |
| 76 | } |
| 77 | select { |
| 78 | case <-done: |
| 79 | case <-time.After(5 * time.Second): |
| 80 | t.Fatal("runner did not return") |
| 81 | } |
| 82 | close(statuses) |
| 83 | want := svc.Status{State: svc.Running, Accepts: svc.AcceptStop | svc.AcceptShutdown} |
| 84 | var last svc.Status |
| 85 | ready := false |
| 86 | for s := range statuses { |
| 87 | ready = ready || s == want |
| 88 | if ready && s != want { |
| 89 | t.Fatalf("stale status after RUNNING: %+v", s) |
| 90 | } |
| 91 | last = s |
| 92 | } |
| 93 | if last != want { |
| 94 | t.Fatalf("last status: got %+v, want %+v", last, want) |
| 95 | } |
| 96 | returned := make(chan struct{}) |
| 97 | go func() { _ = notify.Ready(); close(returned) }() |
| 98 | select { |
| 99 | case <-returned: |
| 100 | case <-time.After(5 * time.Second): |
| 101 | t.Fatal("Ready blocked after runner returned") |
| 102 | } |
| 103 | }) |
| 104 | } |
| 105 | } |
| 106 | |
| 107 | func TestRunnerStopRacesReady(t *testing.T) { |
| 108 | const envKey = "CADDY_TEST_SERVICE_CONTROL" |
| 109 | controls := map[string]svc.Cmd{"stop": svc.Stop, "shutdown": svc.Shutdown} |
| 110 | if name := os.Getenv(envKey); name != "" { |
| 111 | requests := make(chan svc.ChangeRequest) |
| 112 | statuses := make(chan svc.Status) |
| 113 | go runner{}.Execute(nil, requests, statuses) |
| 114 | go func() { |
| 115 | for s := range statuses { |
| 116 | fmt.Println(s.State) |
| 117 | } |
| 118 | }() |
| 119 | go func() { |
| 120 | for { |
| 121 | _ = notify.Ready() |
| 122 | } |
| 123 | }() |
| 124 | requests <- svc.ChangeRequest{Cmd: controls[name]} |
| 125 | time.Sleep(10 * time.Second) |
| 126 | os.Exit(5) // Stop/Shutdown must exit the process before this fallback. |
| 127 | } |
| 128 | for name := range controls { |
| 129 | t.Run(name, func(t *testing.T) { |
| 130 | ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) |
| 131 | defer cancel() |
| 132 | child := exec.CommandContext(ctx, os.Args[0], "-test.run=^TestRunnerStopRacesReady$", "-test.count=1") |
| 133 | child.Env = append(os.Environ(), envKey+"="+name) |
| 134 | out, err := child.Output() |
| 135 | if err != nil { |
| 136 | t.Fatalf("child failed: %v; output:\n%s", err, out) |
| 137 | } |
| 138 | stopping := false |
| 139 | for line := range strings.FieldsSeq(string(out)) { |
| 140 | stopping = stopping || line == fmt.Sprint(svc.StopPending) |
| 141 | if stopping && line == fmt.Sprint(svc.Running) { |
| 142 | t.Fatalf("RUNNING after STOP_PENDING:\n%s", out) |
| 143 | } |
| 144 | } |
| 145 | if !stopping { |
| 146 | t.Fatalf("STOP_PENDING missing:\n%s", out) |
| 147 | } |
| 148 | }) |
| 149 | } |
| 150 | } |