1// Copyright 2022 The Android Open Source Project 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 report 16 17import ( 18 "context" 19 "errors" 20 "fmt" 21 "reflect" 22 "strconv" 23 "testing" 24 25 "tools/treble/build/report/app" 26) 27 28type reportTest struct { 29 manifest *app.RepoManifest 30 commands map[string]*app.BuildCommand 31 inputs map[string]*app.BuildInput 32 queries map[string]*app.BuildQuery 33 paths map[string]map[string]*app.BuildPath 34 multipaths map[string]map[string][]*app.BuildPath 35 projects map[string]*app.GitProject 36 commits map[*app.GitProject]map[string]*app.GitCommit 37 38 deps *app.BuildDeps 39 projectCommits map[string]int 40} 41 42func (r *reportTest) Manifest(filename string) (*app.RepoManifest, error) { 43 var err error 44 out := r.manifest 45 if out == nil { 46 err = errors.New(fmt.Sprintf("No manifest named %s", filename)) 47 } 48 return r.manifest, err 49} 50func (r *reportTest) Command(ctx context.Context, target string) (*app.BuildCommand, error) { 51 var err error 52 out := r.commands[target] 53 if out == nil { 54 err = errors.New(fmt.Sprintf("No command for target %s", target)) 55 } 56 return out, err 57} 58 59func (r *reportTest) Input(ctx context.Context, target string) (*app.BuildInput, error) { 60 var err error 61 out := r.inputs[target] 62 if out == nil { 63 err = errors.New(fmt.Sprintf("No inputs for target %s", target)) 64 } 65 return out, err 66} 67 68func (r *reportTest) Query(ctx context.Context, target string) (*app.BuildQuery, error) { 69 var err error 70 out := r.queries[target] 71 if out == nil { 72 err = errors.New(fmt.Sprintf("No queries for target %s", target)) 73 } 74 return out, err 75} 76 77func (r *reportTest) Path(ctx context.Context, target string, dependency string) (*app.BuildPath, error) { 78 return r.paths[target][dependency], nil 79} 80 81func (r *reportTest) Paths(ctx context.Context, target string, dependency string) ([]*app.BuildPath, error) { 82 return r.multipaths[target][dependency], nil 83} 84 85func (r *reportTest) Deps(ctx context.Context) (*app.BuildDeps, error) { 86 return r.deps, nil 87} 88func (r *reportTest) Project(ctx context.Context, path string, gitDir string, remote string, revision string) (*app.GitProject, error) { 89 var err error 90 out := r.projects[path] 91 if out == nil { 92 err = errors.New(fmt.Sprintf("No projects for target %s", path)) 93 } 94 return out, err 95} 96func (r *reportTest) PopulateFiles(ctx context.Context, proj *app.GitProject, upstream string) error { 97 return nil 98} 99func (r *reportTest) CommitInfo(ctx context.Context, proj *app.GitProject, sha string) (*app.GitCommit, error) { 100 var err error 101 out := r.commits[proj][sha] 102 if out == nil { 103 err = errors.New(fmt.Sprintf("No commit for sha %s", sha)) 104 } 105 return out, err 106} 107 108// Helper routine used in test function to create array of unique names 109func createStrings(name string, count int) []string { 110 var out []string 111 for i := 0; i < count; i++ { 112 out = append(out, name+strconv.Itoa(i)) 113 } 114 return out 115} 116 117// Project names used in tests 118func projName(i int) string { 119 return "proj." + strconv.Itoa(i) 120} 121 122func fileName(i int) (filename string, sha string) { 123 iString := strconv.Itoa(i) 124 return "source." + iString, "sha." + iString 125} 126func createFile(i int) *app.GitTreeObj { 127 fname, sha := fileName(i) 128 return &app.GitTreeObj{Permissions: "100644", Type: "blob", Filename: fname, Sha: sha} 129} 130func createProject(name string) *app.GitProject { 131 return &app.GitProject{ 132 RepoDir: name, WorkDir: name, GitDir: ".git", Remote: "origin", 133 RemoteUrl: "origin_url", Revision: name + "_sha", 134 Files: make(map[string]*app.GitTreeObj)} 135 136} 137 138// Create basic test data for given inputs 139func createTest(projCount int, fileCount int) *reportTest { 140 test := &reportTest{ 141 manifest: &app.RepoManifest{ 142 Remotes: []app.RepoRemote{{Name: "remote1", Revision: "revision_1"}}, 143 Default: app.RepoDefault{Remote: "remote1", Revision: "revision_2"}, 144 Projects: []app.RepoProject{}, 145 }, 146 commands: map[string]*app.BuildCommand{}, 147 inputs: map[string]*app.BuildInput{}, 148 queries: map[string]*app.BuildQuery{}, 149 projects: map[string]*app.GitProject{}, 150 commits: map[*app.GitProject]map[string]*app.GitCommit{}, 151 } 152 153 // Create projects with files 154 for i := 0; i <= projCount; i++ { 155 name := projName(i) 156 157 proj := createProject(name) 158 159 for i := 0; i <= fileCount; i++ { 160 treeObj := createFile(i) 161 proj.Files[treeObj.Filename] = treeObj 162 163 } 164 test.projects[name] = proj 165 test.manifest.Projects = append(test.manifest.Projects, 166 app.RepoProject{Groups: "group", Name: name, Revision: "sha", Path: name}) 167 168 } 169 return test 170} 171 172func Test_report(t *testing.T) { 173 174 test := createTest(10, 20) 175 176 // Test cases will specify input file by project and file index 177 type inputFile struct { 178 proj int 179 file int 180 } 181 182 targetDefs := []struct { 183 name string // Target name 184 cmds int // Number of build steps 185 inputTargets int // Number of input targets 186 outputTargets int // Number of output targets 187 inputFiles []inputFile // Input files for target 188 }{ 189 { 190 name: "target", 191 cmds: 7, 192 inputTargets: 4, 193 outputTargets: 7, 194 inputFiles: []inputFile{{proj: 0, file: 1}, {proj: 1, file: 0}}, 195 }, 196 { 197 name: "target2", 198 cmds: 0, 199 inputTargets: 0, 200 outputTargets: 0, 201 inputFiles: []inputFile{{proj: 0, file: 1}, {proj: 0, file: 2}, {proj: 1, file: 0}}, 202 }, 203 { 204 name: "null_target", 205 cmds: 0, 206 inputTargets: 0, 207 outputTargets: 0, 208 inputFiles: []inputFile{}, 209 }, 210 } 211 212 // Create target data based on definitions 213 var targets []string 214 215 // Build expected output while creating the targets 216 resTargets := make(map[string]*app.BuildTarget) 217 218 for _, target := range targetDefs { 219 220 res := &app.BuildTarget{Name: target.name, 221 Steps: target.cmds, 222 FileCount: len(target.inputFiles), 223 Projects: make(map[string]*app.GitProject), 224 } 225 226 // Add files to the build target 227 var inputFiles []string 228 for _, in := range target.inputFiles { 229 // Get project by name 230 pName := projName(in.proj) 231 bf := createFile(in.file) 232 p := test.projects[pName] 233 234 inputFiles = append(inputFiles, 235 fmt.Sprintf("%s/%s", p.WorkDir, bf.Filename)) 236 237 if _, exists := res.Projects[pName]; !exists { 238 res.Projects[pName] = createProject(pName) 239 } 240 res.Projects[pName].Files[bf.Filename] = bf 241 } 242 243 // Create test data 244 test.commands[target.name] = &app.BuildCommand{Target: target.name, Cmds: createStrings("cmd.", target.cmds)} 245 test.inputs[target.name] = &app.BuildInput{Target: target.name, Files: inputFiles} 246 test.queries[target.name] = &app.BuildQuery{ 247 Target: target.name, 248 Inputs: createStrings("target.in.", target.inputTargets), 249 Outputs: createStrings("target.out.", target.outputTargets)} 250 251 targets = append(targets, target.name) 252 resTargets[res.Name] = res 253 } 254 255 rtx := &Context{RepoBase: "/src", Repo: test, Build: test, Project: test, WorkerCount: 1, BuildWorkerCount: 1} 256 rtx.ResolveProjectMap(nil, "test_file", "") 257 req := &app.ReportRequest{Targets: targets} 258 rsp, err := RunReport(nil, rtx, req) 259 if err != nil { 260 t.Errorf("Failed to run report for request %+v", req) 261 } else { 262 if !reflect.DeepEqual(rsp.Targets, resTargets) { 263 t.Errorf("Got targets %+v, expected %+v", rsp.Targets, resTargets) 264 } 265 } 266} 267