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  }