1// Copyright 2024 Google Inc. All rights reserved. 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 15package find_input_delta_lib 16 17import ( 18 "errors" 19 "io/fs" 20 "testing" 21 "testing/fstest" 22 "time" 23 24 // For Assert*. 25 "android/soong/android" 26 27 fid_proto "android/soong/cmd/find_input_delta/find_input_delta_proto_internal" 28 "google.golang.org/protobuf/proto" 29) 30 31// Various state files 32 33func marshalProto(t *testing.T, message proto.Message) []byte { 34 data, err := proto.Marshal(message) 35 if err != nil { 36 t.Errorf("%v", err) 37 } 38 return data 39} 40 41func protoFile(name string, mtime_nsec int64, hash string, contents []*fid_proto.PartialCompileInput) (pci *fid_proto.PartialCompileInput) { 42 pci = &fid_proto.PartialCompileInput{ 43 Name: proto.String(name), 44 } 45 if mtime_nsec != 0 { 46 pci.MtimeNsec = proto.Int64(mtime_nsec) 47 } 48 if len(hash) > 0 { 49 pci.Hash = proto.String(hash) 50 } 51 if contents != nil { 52 pci.Contents = contents 53 } 54 return 55} 56 57func TestLoadState(t *testing.T) { 58 testCases := []struct { 59 Name string 60 Filename string 61 Mapfs fs.ReadFileFS 62 Expected *fid_proto.PartialCompileInputs 63 Err error 64 }{ 65 { 66 Name: "missing file", 67 Filename: "missing", 68 Mapfs: fstest.MapFS{}, 69 Expected: &fid_proto.PartialCompileInputs{}, 70 Err: nil, 71 }, 72 { 73 Name: "bad file", 74 Filename: ".", 75 Mapfs: OsFs, 76 Expected: &fid_proto.PartialCompileInputs{}, 77 Err: errors.New("read failed"), 78 }, 79 { 80 Name: "file with mtime", 81 Filename: "state.old", 82 Mapfs: fstest.MapFS{ 83 "state.old": &fstest.MapFile{ 84 Data: marshalProto(t, &fid_proto.PartialCompileInputs{ 85 InputFiles: []*fid_proto.PartialCompileInput{ 86 protoFile("input1", 100, "", nil), 87 }, 88 }), 89 }, 90 }, 91 Expected: &fid_proto.PartialCompileInputs{ 92 InputFiles: []*fid_proto.PartialCompileInput{ 93 protoFile("input1", 100, "", nil), 94 }, 95 }, 96 Err: nil, 97 }, 98 { 99 Name: "file with mtime and hash", 100 Filename: "state.old", 101 Mapfs: fstest.MapFS{ 102 "state.old": &fstest.MapFile{ 103 Data: marshalProto(t, &fid_proto.PartialCompileInputs{ 104 InputFiles: []*fid_proto.PartialCompileInput{ 105 protoFile("input1", 100, "crc:crc_value", nil), 106 }, 107 }), 108 }, 109 }, 110 Expected: &fid_proto.PartialCompileInputs{ 111 InputFiles: []*fid_proto.PartialCompileInput{ 112 protoFile("input1", 100, "crc:crc_value", nil), 113 }, 114 }, 115 Err: nil, 116 }, 117 } 118 for _, tc := range testCases { 119 actual, err := LoadState(tc.Filename, tc.Mapfs) 120 if tc.Err == nil { 121 android.AssertSame(t, tc.Name, tc.Err, err) 122 } else if err == nil { 123 t.Errorf("%s: expected error, did not get one", tc.Name) 124 } 125 if !proto.Equal(tc.Expected, actual) { 126 t.Errorf("%s: expected %v, actual %v", tc.Name, tc.Expected, actual) 127 } 128 } 129} 130 131func TestCreateState(t *testing.T) { 132 testCases := []struct { 133 Name string 134 Inputs []string 135 Inspect bool 136 Mapfs StatReadFileFS 137 Expected *fid_proto.PartialCompileInputs 138 Err error 139 }{ 140 { 141 Name: "no inputs", 142 Inputs: []string{}, 143 Mapfs: fstest.MapFS{}, 144 Expected: &fid_proto.PartialCompileInputs{}, 145 Err: nil, 146 }, 147 { 148 Name: "files found", 149 Inputs: []string{"baz", "foo", "bar"}, 150 Mapfs: fstest.MapFS{ 151 "foo": &fstest.MapFile{ModTime: time.Unix(0, 100).UTC()}, 152 "baz": &fstest.MapFile{ModTime: time.Unix(0, 300).UTC()}, 153 "bar": &fstest.MapFile{ModTime: time.Unix(0, 200).UTC()}, 154 }, 155 Expected: &fid_proto.PartialCompileInputs{ 156 InputFiles: []*fid_proto.PartialCompileInput{ 157 // Files are always sorted. 158 protoFile("bar", 200, "", nil), 159 protoFile("baz", 300, "", nil), 160 protoFile("foo", 100, "", nil), 161 }, 162 }, 163 Err: nil, 164 }, 165 } 166 for _, tc := range testCases { 167 actual, err := CreateState(tc.Inputs, tc.Inspect, tc.Mapfs) 168 if tc.Err == nil { 169 android.AssertSame(t, tc.Name, tc.Err, err) 170 } else if err == nil { 171 t.Errorf("%s: expected error, did not get one", tc.Name) 172 } 173 if !proto.Equal(tc.Expected, actual) { 174 t.Errorf("%s: expected %v, actual %v", tc.Name, tc.Expected, actual) 175 } 176 } 177} 178 179func TestCompareInternalState(t *testing.T) { 180 testCases := []struct { 181 Name string 182 Target string 183 Prior *fid_proto.PartialCompileInputs 184 New *fid_proto.PartialCompileInputs 185 Expected *FileList 186 }{ 187 { 188 Name: "prior is empty", 189 Target: "foo", 190 Prior: &fid_proto.PartialCompileInputs{}, 191 New: &fid_proto.PartialCompileInputs{ 192 InputFiles: []*fid_proto.PartialCompileInput{ 193 protoFile("file1", 100, "", nil), 194 }, 195 }, 196 Expected: &FileList{ 197 Name: "foo", 198 Additions: []string{"file1"}, 199 }, 200 }, 201 { 202 Name: "one each add modify delete", 203 Target: "foo", 204 Prior: &fid_proto.PartialCompileInputs{ 205 InputFiles: []*fid_proto.PartialCompileInput{ 206 protoFile("file0", 100, "", nil), 207 protoFile("file1", 100, "", nil), 208 protoFile("file2", 200, "", nil), 209 }, 210 }, 211 New: &fid_proto.PartialCompileInputs{ 212 InputFiles: []*fid_proto.PartialCompileInput{ 213 protoFile("file0", 100, "", nil), 214 protoFile("file1", 200, "", nil), 215 protoFile("file3", 300, "", nil), 216 }, 217 }, 218 Expected: &FileList{ 219 Name: "foo", 220 Additions: []string{"file3"}, 221 Changes: []FileList{FileList{Name: "file1"}}, 222 Deletions: []string{"file2"}, 223 }, 224 }, 225 { 226 Name: "interior one each add modify delete", 227 Target: "bar", 228 Prior: &fid_proto.PartialCompileInputs{ 229 InputFiles: []*fid_proto.PartialCompileInput{ 230 protoFile("file1", 405, "", []*fid_proto.PartialCompileInput{ 231 protoFile("innerC", 400, "crc32:11111111", nil), 232 protoFile("innerD", 400, "crc32:44444444", nil), 233 }), 234 }, 235 }, 236 New: &fid_proto.PartialCompileInputs{ 237 InputFiles: []*fid_proto.PartialCompileInput{ 238 protoFile("file1", 505, "", []*fid_proto.PartialCompileInput{ 239 protoFile("innerA", 400, "crc32:55555555", nil), 240 protoFile("innerC", 500, "crc32:66666666", nil), 241 }), 242 }, 243 }, 244 Expected: &FileList{ 245 Name: "bar", 246 Changes: []FileList{FileList{ 247 Name: "file1", 248 Additions: []string{"innerA"}, 249 Changes: []FileList{FileList{Name: "innerC"}}, 250 Deletions: []string{"innerD"}, 251 }}, 252 }, 253 }, 254 } 255 for _, tc := range testCases { 256 actual := CompareInternalState(tc.Prior, tc.New, tc.Target) 257 if !tc.Expected.Equal(actual) { 258 t.Errorf("%s: expected %v, actual %v", tc.Name, tc.Expected, actual) 259 } 260 } 261} 262 263func TestCompareInspectExtsZipRegexp(t *testing.T) { 264 testCases := []struct { 265 Name string 266 Expected bool 267 }{ 268 {Name: ".jar", Expected: true}, 269 {Name: ".jar5", Expected: true}, 270 {Name: ".apex", Expected: true}, 271 {Name: ".apex9", Expected: true}, 272 {Name: ".apexx", Expected: false}, 273 {Name: ".apk", Expected: true}, 274 {Name: ".apk3", Expected: true}, 275 {Name: ".go", Expected: false}, 276 } 277 for _, tc := range testCases { 278 actual := InspectExtsZipRegexp.Match([]byte(tc.Name)) 279 if tc.Expected != actual { 280 t.Errorf("%s: expected %v, actual %v", tc.Name, tc.Expected, actual) 281 } 282 } 283} 284