// Copyright 2022 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package report import ( "context" "errors" "fmt" "reflect" "strconv" "testing" "tools/treble/build/report/app" ) type reportTest struct { manifest *app.RepoManifest commands map[string]*app.BuildCommand inputs map[string]*app.BuildInput queries map[string]*app.BuildQuery paths map[string]map[string]*app.BuildPath multipaths map[string]map[string][]*app.BuildPath projects map[string]*app.GitProject commits map[*app.GitProject]map[string]*app.GitCommit deps *app.BuildDeps projectCommits map[string]int } func (r *reportTest) Manifest(filename string) (*app.RepoManifest, error) { var err error out := r.manifest if out == nil { err = errors.New(fmt.Sprintf("No manifest named %s", filename)) } return r.manifest, err } func (r *reportTest) Command(ctx context.Context, target string) (*app.BuildCommand, error) { var err error out := r.commands[target] if out == nil { err = errors.New(fmt.Sprintf("No command for target %s", target)) } return out, err } func (r *reportTest) Input(ctx context.Context, target string) (*app.BuildInput, error) { var err error out := r.inputs[target] if out == nil { err = errors.New(fmt.Sprintf("No inputs for target %s", target)) } return out, err } func (r *reportTest) Query(ctx context.Context, target string) (*app.BuildQuery, error) { var err error out := r.queries[target] if out == nil { err = errors.New(fmt.Sprintf("No queries for target %s", target)) } return out, err } func (r *reportTest) Path(ctx context.Context, target string, dependency string) (*app.BuildPath, error) { return r.paths[target][dependency], nil } func (r *reportTest) Paths(ctx context.Context, target string, dependency string) ([]*app.BuildPath, error) { return r.multipaths[target][dependency], nil } func (r *reportTest) Deps(ctx context.Context) (*app.BuildDeps, error) { return r.deps, nil } func (r *reportTest) Project(ctx context.Context, path string, gitDir string, remote string, revision string) (*app.GitProject, error) { var err error out := r.projects[path] if out == nil { err = errors.New(fmt.Sprintf("No projects for target %s", path)) } return out, err } func (r *reportTest) PopulateFiles(ctx context.Context, proj *app.GitProject, upstream string) error { return nil } func (r *reportTest) CommitInfo(ctx context.Context, proj *app.GitProject, sha string) (*app.GitCommit, error) { var err error out := r.commits[proj][sha] if out == nil { err = errors.New(fmt.Sprintf("No commit for sha %s", sha)) } return out, err } // Helper routine used in test function to create array of unique names func createStrings(name string, count int) []string { var out []string for i := 0; i < count; i++ { out = append(out, name+strconv.Itoa(i)) } return out } // Project names used in tests func projName(i int) string { return "proj." + strconv.Itoa(i) } func fileName(i int) (filename string, sha string) { iString := strconv.Itoa(i) return "source." + iString, "sha." + iString } func createFile(i int) *app.GitTreeObj { fname, sha := fileName(i) return &app.GitTreeObj{Permissions: "100644", Type: "blob", Filename: fname, Sha: sha} } func createProject(name string) *app.GitProject { return &app.GitProject{ RepoDir: name, WorkDir: name, GitDir: ".git", Remote: "origin", RemoteUrl: "origin_url", Revision: name + "_sha", Files: make(map[string]*app.GitTreeObj)} } // Create basic test data for given inputs func createTest(projCount int, fileCount int) *reportTest { test := &reportTest{ manifest: &app.RepoManifest{ Remotes: []app.RepoRemote{{Name: "remote1", Revision: "revision_1"}}, Default: app.RepoDefault{Remote: "remote1", Revision: "revision_2"}, Projects: []app.RepoProject{}, }, commands: map[string]*app.BuildCommand{}, inputs: map[string]*app.BuildInput{}, queries: map[string]*app.BuildQuery{}, projects: map[string]*app.GitProject{}, commits: map[*app.GitProject]map[string]*app.GitCommit{}, } // Create projects with files for i := 0; i <= projCount; i++ { name := projName(i) proj := createProject(name) for i := 0; i <= fileCount; i++ { treeObj := createFile(i) proj.Files[treeObj.Filename] = treeObj } test.projects[name] = proj test.manifest.Projects = append(test.manifest.Projects, app.RepoProject{Groups: "group", Name: name, Revision: "sha", Path: name}) } return test } func Test_report(t *testing.T) { test := createTest(10, 20) // Test cases will specify input file by project and file index type inputFile struct { proj int file int } targetDefs := []struct { name string // Target name cmds int // Number of build steps inputTargets int // Number of input targets outputTargets int // Number of output targets inputFiles []inputFile // Input files for target }{ { name: "target", cmds: 7, inputTargets: 4, outputTargets: 7, inputFiles: []inputFile{{proj: 0, file: 1}, {proj: 1, file: 0}}, }, { name: "target2", cmds: 0, inputTargets: 0, outputTargets: 0, inputFiles: []inputFile{{proj: 0, file: 1}, {proj: 0, file: 2}, {proj: 1, file: 0}}, }, { name: "null_target", cmds: 0, inputTargets: 0, outputTargets: 0, inputFiles: []inputFile{}, }, } // Create target data based on definitions var targets []string // Build expected output while creating the targets resTargets := make(map[string]*app.BuildTarget) for _, target := range targetDefs { res := &app.BuildTarget{Name: target.name, Steps: target.cmds, FileCount: len(target.inputFiles), Projects: make(map[string]*app.GitProject), } // Add files to the build target var inputFiles []string for _, in := range target.inputFiles { // Get project by name pName := projName(in.proj) bf := createFile(in.file) p := test.projects[pName] inputFiles = append(inputFiles, fmt.Sprintf("%s/%s", p.WorkDir, bf.Filename)) if _, exists := res.Projects[pName]; !exists { res.Projects[pName] = createProject(pName) } res.Projects[pName].Files[bf.Filename] = bf } // Create test data test.commands[target.name] = &app.BuildCommand{Target: target.name, Cmds: createStrings("cmd.", target.cmds)} test.inputs[target.name] = &app.BuildInput{Target: target.name, Files: inputFiles} test.queries[target.name] = &app.BuildQuery{ Target: target.name, Inputs: createStrings("target.in.", target.inputTargets), Outputs: createStrings("target.out.", target.outputTargets)} targets = append(targets, target.name) resTargets[res.Name] = res } rtx := &Context{RepoBase: "/src", Repo: test, Build: test, Project: test, WorkerCount: 1, BuildWorkerCount: 1} rtx.ResolveProjectMap(nil, "test_file", "") req := &app.ReportRequest{Targets: targets} rsp, err := RunReport(nil, rtx, req) if err != nil { t.Errorf("Failed to run report for request %+v", req) } else { if !reflect.DeepEqual(rsp.Targets, resTargets) { t.Errorf("Got targets %+v, expected %+v", rsp.Targets, resTargets) } } }