go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/lucictx/lucictx_test.go (about) 1 // Copyright 2016 The LUCI 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 lucictx 16 17 import ( 18 "bytes" 19 "context" 20 "encoding/json" 21 "io/ioutil" 22 "os" 23 "os/exec" 24 "path/filepath" 25 "testing" 26 27 . "github.com/smartystreets/goconvey/convey" 28 . "go.chromium.org/luci/common/testing/assertions" 29 30 "go.chromium.org/luci/common/system/environ" 31 ) 32 33 func rawctx(content string) func() { 34 tf, err := ioutil.TempFile(os.TempDir(), "lucictx_test.") 35 if err != nil { 36 panic(err) 37 } 38 _, err = tf.WriteString(content) 39 if err != nil { 40 panic(err) 41 } 42 tf.Close() 43 os.Setenv(EnvKey, tf.Name()) 44 45 return func() { os.Remove(tf.Name()) } 46 } 47 48 func TestLUCIContextInitialization(t *testing.T) { 49 // t.Parallel() because of os.Environ manipulation 50 51 Convey("extractFromEnv", t, func() { 52 os.Unsetenv(EnvKey) 53 buf := &bytes.Buffer{} 54 55 Convey("works with missing envvar", func() { 56 So(extractFromEnv(buf), ShouldResemble, &lctx{}) 57 So(buf.String(), ShouldEqual, "") 58 _, ok := os.LookupEnv(EnvKey) 59 So(ok, ShouldBeFalse) 60 }) 61 62 Convey("works with bad envvar", func() { 63 os.Setenv(EnvKey, "sup") 64 So(extractFromEnv(buf), ShouldResemble, &lctx{}) 65 So(buf.String(), ShouldContainSubstring, "Could not open LUCI_CONTEXT file") 66 }) 67 68 Convey("works with bad file", func() { 69 defer rawctx(`"not a map"`)() 70 71 So(extractFromEnv(buf), ShouldResemble, &lctx{}) 72 So(buf.String(), ShouldContainSubstring, "cannot unmarshal string into Go value") 73 }) 74 75 Convey("ignores bad sections", func() { 76 defer rawctx(`{"hi": {"hello_there": 10}, "not": "good"}`)() 77 78 blob := json.RawMessage(`{"hello_there":10}`) 79 So(extractFromEnv(buf).sections, ShouldResemble, map[string]*json.RawMessage{ 80 "hi": &blob, 81 }) 82 So(buf.String(), ShouldContainSubstring, `section "not": Not a map`) 83 }) 84 }) 85 } 86 87 func TestLUCIContextMethods(t *testing.T) { 88 // t.Parallel() manipulates environ 89 defer rawctx(`{"hi": {"hello_there": 10}}`)() 90 externalContext = extractFromEnv(nil) 91 92 Convey("lctx", t, func() { 93 c := context.Background() 94 95 Convey("Get/Set", func() { 96 Convey("defaults to env values", func() { 97 h := &TestStructure{} 98 So(Get(c, "hi", h), ShouldBeNil) 99 So(h, ShouldResembleProto, &TestStructure{HelloThere: 10}) 100 }) 101 102 Convey("nop for missing sections", func() { 103 h := &TestStructure{} 104 So(Get(c, "wut", h), ShouldBeNil) 105 So(h, ShouldResembleProto, &TestStructure{}) 106 }) 107 108 Convey("can add section", func() { 109 c := Set(c, "wut", &TestStructure{HelloThere: 100}) 110 111 h := &TestStructure{} 112 So(Get(c, "wut", h), ShouldBeNil) 113 So(h, ShouldResembleProto, &TestStructure{HelloThere: 100}) 114 115 So(Get(c, "hi", h), ShouldBeNil) 116 So(h, ShouldResembleProto, &TestStructure{HelloThere: 10}) 117 }) 118 119 Convey("can override section", func() { 120 c := Set(c, "hi", &TestStructure{HelloThere: 100}) 121 122 h := &TestStructure{} 123 So(Get(c, "hi", h), ShouldBeNil) 124 So(h, ShouldResembleProto, &TestStructure{HelloThere: 100}) 125 }) 126 127 Convey("can remove section", func() { 128 c := Set(c, "hi", nil) 129 130 h := &TestStructure{} 131 So(Get(c, "hi", h), ShouldBeNil) 132 So(h, ShouldResembleProto, &TestStructure{HelloThere: 0}) 133 }) 134 135 Convey("allow unknown field", func() { 136 blob := json.RawMessage(`{"hello_there":10, "unknown_field": "unknown"}`) 137 newLctx := externalContext.clone() 138 newLctx.sections["hi"] = &blob 139 c := context.WithValue(context.Background(), &lctxKey, newLctx) 140 141 h := &TestStructure{} 142 So(Get(c, "hi", h), ShouldBeNil) 143 So(h, ShouldResembleProto, &TestStructure{HelloThere: 10}) 144 }) 145 }) 146 147 Convey("Export", func() { 148 Convey("empty export is a noop", func() { 149 c = Set(c, "hi", nil) 150 e, err := Export(c) 151 So(err, ShouldBeNil) 152 So(e, ShouldHaveSameTypeAs, &nullExport{}) 153 }) 154 155 Convey("exporting with content gives a live export", func() { 156 e, err := Export(c) 157 So(err, ShouldBeNil) 158 So(e, ShouldHaveSameTypeAs, &liveExport{}) 159 defer e.Close() 160 cmd := exec.Command("something", "something") 161 e.SetInCmd(cmd) 162 path, ok := environ.New(cmd.Env).Lookup(EnvKey) 163 So(ok, ShouldBeTrue) 164 165 // There's a valid JSON there. 166 blob, err := os.ReadFile(path) 167 So(err, ShouldBeNil) 168 So(string(blob), ShouldEqual, `{"hi": {"hello_there": 10}}`) 169 170 // And it is in fact located in same file as was supplied externally. 171 So(path, ShouldEqual, externalContext.path) 172 }) 173 174 Convey("Export reuses files", func() { 175 c := Set(c, "blah", &TestStructure{}) 176 177 e1, err := Export(c) 178 So(err, ShouldBeNil) 179 So(e1, ShouldHaveSameTypeAs, &liveExport{}) 180 defer func() { 181 if e1 != nil { 182 e1.Close() 183 } 184 }() 185 186 e2, err := Export(c) 187 So(err, ShouldBeNil) 188 So(e2, ShouldHaveSameTypeAs, &liveExport{}) 189 defer func() { 190 if e2 != nil { 191 e2.Close() 192 } 193 }() 194 195 // Exact same backing file. 196 So(e1.(*liveExport).path, ShouldEqual, e2.(*liveExport).path) 197 198 // It really exists. 199 path := e1.(*liveExport).path 200 _, err = os.Stat(path) 201 So(err, ShouldBeNil) 202 203 // Closing one export keeps the file open. 204 e1.Close() 205 e1 = nil 206 _, err = os.Stat(path) 207 So(err, ShouldBeNil) 208 209 // Closing both exports removes the file from disk. 210 e2.Close() 211 e2 = nil 212 _, err = os.Stat(path) 213 So(os.IsNotExist(err), ShouldBeTrue) 214 }) 215 216 Convey("ExportInto creates new files", func() { 217 tmp, err := ioutil.TempDir("", "luci_ctx") 218 So(err, ShouldBeNil) 219 defer func() { 220 os.RemoveAll(tmp) 221 }() 222 223 c := Set(c, "blah", &TestStructure{}) 224 225 e1, err := ExportInto(c, tmp) 226 So(err, ShouldBeNil) 227 p1 := e1.(*liveExport).path 228 229 e2, err := ExportInto(c, tmp) 230 So(err, ShouldBeNil) 231 p2 := e2.(*liveExport).path 232 233 // Two different files, both under 'tmp'. 234 So(p1, ShouldNotEqual, p2) 235 So(filepath.Dir(p1), ShouldEqual, tmp) 236 So(filepath.Dir(p2), ShouldEqual, tmp) 237 238 // Both exist. 239 _, err = os.Stat(p1) 240 So(err, ShouldBeNil) 241 _, err = os.Stat(p2) 242 So(err, ShouldBeNil) 243 244 // Closing one still keeps the other open. 245 So(e1.Close(), ShouldBeNil) 246 _, err = os.Stat(p1) 247 So(os.IsNotExist(err), ShouldBeTrue) 248 _, err = os.Stat(p2) 249 So(err, ShouldBeNil) 250 }) 251 }) 252 }) 253 }