go.fuchsia.dev/infra@v0.0.0-20240507153436-9b593402251b/cmd/recipe_wrapper/recipes/recipes_test.go (about) 1 // Copyright 2023 The Fuchsia Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package recipes 6 7 import ( 8 "context" 9 "encoding/json" 10 "os" 11 "path/filepath" 12 "testing" 13 14 "github.com/google/go-cmp/cmp" 15 buildbucketpb "go.chromium.org/luci/buildbucket/proto" 16 "google.golang.org/protobuf/types/known/structpb" 17 ) 18 19 func TestResolveBuildProperties(t *testing.T) { 20 t.Parallel() 21 22 tests := []struct { 23 name string 24 inputProperties map[string]any 25 versionedProperties map[string]any 26 expectProperties map[string]any 27 expectErr bool 28 }{ 29 { 30 name: "passes through versioned properties", 31 inputProperties: map[string]any{}, 32 versionedProperties: map[string]any{ 33 "foo": "bar", 34 }, 35 expectProperties: map[string]any{ 36 "foo": "bar", 37 }, 38 }, 39 { 40 name: "request properties override versioned properties", 41 inputProperties: map[string]any{ 42 "foo": "request_value", 43 }, 44 versionedProperties: map[string]any{ 45 "foo": "versioned_value", 46 "bar": "baz", 47 }, 48 expectProperties: map[string]any{ 49 "foo": "request_value", 50 "bar": "baz", 51 }, 52 }, 53 { 54 // "recipe" is a special case, and the only property for which the 55 // versioned value should always override the value in the build 56 // input. 57 name: "versioned recipe property overrides request property", 58 inputProperties: map[string]any{ 59 "foo": "request_foo", 60 "recipe": "request_recipe", 61 }, 62 versionedProperties: map[string]any{ 63 "foo": "versioned_foo", 64 "bar": "versioned_bar", 65 "recipe": "versioned_recipe", 66 }, 67 expectProperties: map[string]any{ 68 "foo": "request_foo", 69 "bar": "versioned_bar", 70 "recipe": "versioned_recipe", 71 }, 72 }, 73 { 74 // On the off chance that the versioned properties don't specify a 75 // recipe, we should respect the recipe from the build input. 76 name: "request recipe used if no versioned property", 77 inputProperties: map[string]any{ 78 "recipe": "request_recipe", 79 }, 80 versionedProperties: map[string]any{}, 81 expectProperties: map[string]any{ 82 "recipe": "request_recipe", 83 }, 84 }, 85 } 86 87 for _, test := range tests { 88 t.Run(test.name, func(t *testing.T) { 89 ctx := context.Background() 90 builder := &buildbucketpb.BuilderID{ 91 Project: "fuchsia", Bucket: "ci", Builder: "bldr", 92 } 93 build := &buildbucketpb.Build{ 94 Builder: builder, 95 Input: &buildbucketpb.Build_Input{}, 96 } 97 var err error 98 build.Input.Properties, err = structpb.NewStruct(test.inputProperties) 99 if err != nil { 100 t.Fatal(err) 101 } 102 integrationDir := t.TempDir() 103 propertiesPath := filepath.Join( 104 integrationDir, 105 propertiesFileForBuilder(builder)) 106 jsonProps, err := json.Marshal(test.versionedProperties) 107 if err != nil { 108 t.Fatal(err) 109 } 110 writeFile(t, propertiesPath, []byte(jsonProps)) 111 112 if err := resolveBuildProperties(ctx, integrationDir, build); err != nil { 113 if !test.expectErr { 114 t.Fatalf("got unexpected error: %s", err) 115 } 116 return 117 } else if test.expectErr { 118 t.Fatalf("expected error, got nil") 119 } 120 if diff := cmp.Diff(test.expectProperties, build.Input.Properties.AsMap()); diff != "" { 121 t.Errorf("resolveBuildProperties(): output differs (-want, +got):\n%s", diff) 122 } 123 }) 124 } 125 } 126 127 func TestReadVersionedProperties(t *testing.T) { 128 builder := &buildbucketpb.BuilderID{ 129 Project: "fuchsia", Bucket: "ci", Builder: "bldr", 130 } 131 132 props := map[string]any{ 133 "nested_map": map[string]any{ 134 "server": "url", 135 }, 136 "number": float64(1234), 137 "bool": true, 138 } 139 140 integrationDir := t.TempDir() 141 142 writeFile( 143 t, 144 filepath.Join(integrationDir, propertiesFileForBuilder(builder)), 145 mustMarshalJSON(t, props)) 146 147 got, err := versionedProperties(context.Background(), integrationDir, builder) 148 if err != nil { 149 t.Fatal(err) 150 } 151 152 if diff := cmp.Diff(props, got.AsMap()); diff != "" { 153 t.Errorf("readVersionedProperties() output differs (-want, +got):\n%s", diff) 154 } 155 156 // Shadow builders should use same properties file as non-shadow counterparts. 157 shadowBuilder := &buildbucketpb.BuilderID{ 158 Project: "fuchsia", Bucket: "ci.shadow", Builder: "bldr", 159 } 160 got, err = versionedProperties(context.Background(), integrationDir, shadowBuilder) 161 if err != nil { 162 t.Fatal(err) 163 } 164 165 if diff := cmp.Diff(props, got.AsMap()); diff != "" { 166 t.Errorf("readVersionedProperties() output differs (-want, +got):\n%s", diff) 167 } 168 } 169 170 func writeFile(t *testing.T, path string, contents []byte) { 171 t.Helper() 172 if err := os.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil { 173 t.Fatal(err) 174 } 175 if err := os.WriteFile(path, contents, 0o600); err != nil { 176 t.Fatal(err) 177 } 178 } 179 180 func mustMarshalJSON(t *testing.T, obj any) []byte { 181 res, err := json.Marshal(obj) 182 if err != nil { 183 t.Fatal(err) 184 } 185 return res 186 }