| // Copyright 2020 Google Inc. All Rights Reserved. |
| // |
| // 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 main |
| |
| import ( |
| "bytes" |
| "encoding/json" |
| "fmt" |
| "io/ioutil" |
| "os" |
| "os/exec" |
| "testing" |
| ) |
| |
| const ( |
| gerritURL = "https://cos-review.googlesource.com" |
| fallbackURLs = "https://chromium-review.googlesource.com" |
| gitilesURL = "cos.googlesource.com" |
| manifestRepo = "cos/manifest-snapshots" |
| ) |
| |
| var ( |
| repoLogFields = []string{"Commits", "InstanceURL", "Repo", "SourceSHA", "TargetSHA", "HasMoreCommits"} |
| commitFields = []string{"SHA", "AuthorName", "CommitterName", "Subject", "Bugs", "ReleaseNote", "CommitTime"} |
| ) |
| |
| type Commit struct { |
| SHA string |
| AuthorName string |
| CommitterName string |
| Subject string |
| Bugs []string |
| CommitTime string |
| ReleaseNote string |
| } |
| |
| func setup() error { |
| cmd := exec.Command("go", "build", "-o", "changelogctl", "main.go") |
| return cmd.Run() |
| } |
| |
| func cleanOutputFiles(source, target string) { |
| additions := filename(source, target) |
| removals := filename(target, source) |
| cmd := exec.Command("rm", additions, removals) |
| cmd.Run() |
| } |
| |
| func filename(source, target string) string { |
| return fmt.Sprintf("%s -> %s.json", source, target) |
| } |
| |
| func fileExists(source, target string) bool { |
| fname := filename(source, target) |
| info, err := os.Stat(fname) |
| if os.IsNotExist(err) || info.IsDir() { |
| return false |
| } |
| return true |
| } |
| |
| func fileContents(source, target string) []byte { |
| fname := filename(source, target) |
| contents, _ := ioutil.ReadFile(fname) |
| return contents |
| } |
| |
| func validateEmptyChangelog(source, target string) bool { |
| contents := fileContents(source, target) |
| return string(contents) == "{}" |
| } |
| |
| // validateCommit verifies if a given interface matches the commit format |
| func validateCommit(input interface{}) bool { |
| commit, ok := input.(map[string]interface{}) |
| if !ok { |
| return false |
| } |
| for _, field := range commitFields { |
| if _, ok := commit[field]; !ok { |
| return false |
| } |
| } |
| return true |
| } |
| |
| func validateRepoLog(input interface{}) bool { |
| repoLog, ok := input.(map[string]interface{}) |
| if !ok { |
| return false |
| } |
| for _, field := range repoLogFields { |
| if _, ok := repoLog[field]; !ok { |
| return false |
| } |
| } |
| commits, ok := repoLog["Commits"] |
| if !ok { |
| return false |
| } |
| if _, ok := commits.([]interface{}); !ok { |
| return false |
| } |
| for _, commit := range commits.([]interface{}) { |
| if !validateCommit(commit) { |
| return false |
| } |
| } |
| return true |
| } |
| |
| func validateChangelogSchema(source, target string) bool { |
| if validateEmptyChangelog(source, target) { |
| return false |
| } |
| contents := fileContents(source, target) |
| var data interface{} |
| err := json.Unmarshal(contents, &data) |
| if err != nil { |
| return false |
| } |
| if _, ok := data.(map[string]interface{}); !ok { |
| return false |
| } |
| for _, val := range data.(map[string]interface{}) { |
| if !validateRepoLog(val) { |
| return false |
| } |
| } |
| return true |
| } |
| |
| func TestChangelog(t *testing.T) { |
| err := setup() |
| if err != nil { |
| t.Fatalf("Error compiling main.go:\n%v", err) |
| } |
| |
| tests := map[string]struct { |
| Source string |
| Target string |
| Args []string |
| ShouldErr bool |
| EmptyAdds bool |
| EmptyRms bool |
| }{ |
| "basic run": { |
| Source: "15050.0.0", |
| Target: "15056.0.0", |
| ShouldErr: false, |
| EmptyAdds: false, |
| EmptyRms: true, |
| }, |
| "with instance and repo": { |
| Source: "15048.0.0", |
| Target: "15049.0.0", |
| Args: []string{"-gob", gitilesURL, "-r", manifestRepo}, |
| ShouldErr: false, |
| EmptyAdds: false, |
| EmptyRms: true, |
| }, |
| "image name": { |
| Source: "cos-rc-85-13310-1034-0", |
| Target: "cos-rc-85-13310-1030-0", |
| ShouldErr: false, |
| EmptyAdds: true, |
| EmptyRms: false, |
| }, |
| "invalid source": { |
| Source: "999999.0.0", |
| Target: "15056.0.0", |
| ShouldErr: true, |
| }, |
| "invalid target": { |
| Source: "15056.0.0", |
| Target: "99999.0.0", |
| ShouldErr: true, |
| }, |
| "invalid instance": { |
| Source: "999999.0.0", |
| Target: "15056.0.0", |
| Args: []string{"-gob", "cos.gg.com", "-repo", manifestRepo}, |
| ShouldErr: true, |
| }, |
| "invalid repo": { |
| Source: "999999.0.0", |
| Target: "15056.0.0", |
| Args: []string{"-gob", gitilesURL, "-repo", "not/arepo"}, |
| ShouldErr: true, |
| }, |
| "same source and target": { |
| Source: "15049.0.0", |
| Target: "15049.0.0", |
| Args: []string{"-gob", gitilesURL, "-r", manifestRepo}, |
| ShouldErr: false, |
| EmptyAdds: true, |
| EmptyRms: true, |
| }, |
| } |
| for name, test := range tests { |
| t.Run(name, func(t *testing.T) { |
| args := append([]string{"-mode", "changelog"}, test.Args...) |
| args = append(args, []string{test.Source, test.Target}...) |
| cmd := exec.Command("./changelogctl", args...) |
| err := cmd.Run() |
| if test.ShouldErr { |
| switch { |
| case err == nil: |
| t.Fatalf("expected error, got nil") |
| case fileExists(test.Source, test.Target): |
| t.Fatalf("expected no files to be created, got additions file") |
| case fileExists(test.Target, test.Source): |
| t.Fatalf("expected no files to be created, got removals file") |
| } |
| } else { |
| switch { |
| case err != nil: |
| t.Fatalf("expected no error, got %v", err) |
| case !fileExists(test.Source, test.Target): |
| t.Fatalf("expected additions file to be created, got no file") |
| case !fileExists(test.Target, test.Source): |
| t.Fatalf("expected removals file to be created, got no file") |
| case test.EmptyAdds && !validateEmptyChangelog(test.Source, test.Target): |
| t.Fatalf("expected empty additions file, got non-empty file") |
| case !test.EmptyAdds && !validateChangelogSchema(test.Source, test.Target): |
| t.Fatalf("expected valid, nonempty additions file, got invalid/empty file") |
| case test.EmptyRms && !validateEmptyChangelog(test.Target, test.Source): |
| t.Fatalf("expected empty removals file, got non-empty file") |
| case !test.EmptyRms && !validateChangelogSchema(test.Target, test.Source): |
| t.Fatalf("expected valid, nonempty removals file, got invalid/empty file") |
| } |
| cleanOutputFiles(test.Source, test.Target) |
| } |
| }) |
| } |
| } |
| |
| func TestFindBuild(t *testing.T) { |
| err := setup() |
| if err != nil { |
| t.Fatalf("Error compiling main.go:\n%v", err) |
| } |
| |
| tests := map[string]struct { |
| CL string |
| Args []string |
| Output string |
| ShouldErr bool |
| }{ |
| "test basic": { |
| CL: "3781", |
| Output: "Build: 12371.1072.0\n", |
| }, |
| "test commit SHA": { |
| CL: "80809c436f1cae4cde117fce34b82f38bdc2fd36", |
| Output: "Build: 12871.1183.0\n", |
| }, |
| "test gerrit fallback": { |
| CL: "2288114", |
| Output: "Build: 15049.0.0\n", |
| }, |
| "test string flags": { |
| CL: "3781", |
| Args: []string{"-gerrit", gerritURL, "-gob", gitilesURL, "-repo", manifestRepo}, |
| Output: "Build: 12371.1072.0\n", |
| }, |
| "test fallback string flags": { |
| CL: "2288114", |
| Args: []string{"-gerrit", gerritURL, "-fallback", fallbackURLs, "-gob", gitilesURL, "-repo", manifestRepo}, |
| Output: "Build: 15049.0.0\n", |
| }, |
| "invalid gob": { |
| CL: "2288114", |
| Args: []string{"-gerrit", gerritURL, "-fallback", fallbackURLs, "-gob", "zop.googlesource.com", "-repo", manifestRepo}, |
| ShouldErr: true, |
| }, |
| "invalid gerrit": { |
| CL: "3781", |
| Args: []string{"-gerrit", "https://zop-review.googlesource.com", "-fallback", fallbackURLs, "-gob", gitilesURL, "-repo", manifestRepo}, |
| ShouldErr: true, |
| }, |
| "invalid fallback": { |
| CL: "2288114", |
| Args: []string{"-gerrit", gerritURL, "-fallback", "https://zop-review.googlesource.com", "-gob", gitilesURL, "-repo", manifestRepo}, |
| ShouldErr: true, |
| }, |
| "non-existant cl": { |
| CL: "9999999999999999999999", |
| ShouldErr: true, |
| }, |
| "unsubmitted cl": { |
| CL: "1540", |
| ShouldErr: true, |
| }, |
| } |
| for name, test := range tests { |
| t.Run(name, func(t *testing.T) { |
| var out bytes.Buffer |
| args := append([]string{"-mode", "findbuild"}, test.Args...) |
| args = append(args, test.CL) |
| cmd := exec.Command("./changelogctl", args...) |
| cmd.Stdout = &out |
| err := cmd.Run() |
| if test.ShouldErr && err == nil { |
| t.Fatalf("expected error, got nil") |
| } else if !test.ShouldErr { |
| switch { |
| case err != nil: |
| t.Fatalf("expected no error, got %v", err) |
| case out.String() != test.Output: |
| t.Fatalf("expected output %s, got %s", test.Output, out.String()) |
| } |
| } |
| }) |
| } |
| } |