models.go
unknown
plain_text
2 years ago
17 kB
2
Indexable
// Copyright 2017 The WPT Dashboard Project. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package shared import ( "encoding/json" "fmt" "time" "cloud.google.com/go/datastore" mapset "github.com/deckarep/golang-set" ) // Product uniquely defines a browser version, running on an OS version. type Product struct { BrowserName string `json:"browser_name"` BrowserVersion string `json:"browser_version"` OSName string `json:"os_name"` OSVersion string `json:"os_version"` } func (p Product) String() string { s := p.BrowserName if p.BrowserVersion != "" { s = fmt.Sprintf("%s-%s", s, p.BrowserVersion) } if p.OSName != "" { s = fmt.Sprintf("%s-%s", s, p.OSName) if p.OSVersion != "" { s = fmt.Sprintf("%s-%s", s, p.OSVersion) } } return s } // ByBrowserName is a []Product sortable by BrowserName values. type ByBrowserName []Product func (e ByBrowserName) Len() int { return len(e) } func (e ByBrowserName) Swap(i, j int) { e[i], e[j] = e[j], e[i] } func (e ByBrowserName) Less(i, j int) bool { return e[i].BrowserName < e[j].BrowserName } // Version is a struct for a parsed version string. type Version struct { Major int Minor *int Build *int Revision *int Channel string } func (v Version) String() string { s := fmt.Sprintf("%v", v.Major) if v.Minor != nil { s = fmt.Sprintf("%s.%v", s, *v.Minor) } if v.Build != nil { s = fmt.Sprintf("%s.%v", s, *v.Build) } if v.Revision != nil { s = fmt.Sprintf("%s.%v", s, *v.Revision) } if v.Channel != "" { s = fmt.Sprintf("%s%s", s, v.Channel) } return s } // ProductAtRevision defines a WPT run for a specific product, at a // specific hash of the WPT repo. type ProductAtRevision struct { Product // The first 10 characters of the SHA1 of the tested WPT revision. // // Deprecated: The authoritative git revision indicator is FullRevisionHash. Revision string `json:"revision"` // The complete SHA1 hash of the tested WPT revision. FullRevisionHash string `json:"full_revision_hash"` } func (p ProductAtRevision) String() string { return fmt.Sprintf("%s@%s", p.Product.String(), p.Revision) } // TestRun stores metadata for a test run (produced by run/run.py) type TestRun struct { ID int64 `json:"id" datastore:"-"` ProductAtRevision // URL for summary of results, which is derived from raw results. ResultsURL string `json:"results_url"` // Time when the test run metadata was first created. CreatedAt time.Time `json:"created_at"` // Time when the test run started. TimeStart time.Time `json:"time_start"` // Time when the test run ended. TimeEnd time.Time `json:"time_end"` // URL for raw results JSON object. Resembles the JSON output of the // wpt report tool. RawResultsURL string `json:"raw_results_url"` // Labels for the test run. Labels []string `json:"labels"` } // IsExperimental returns true if the run is labelled experimental. func (r TestRun) IsExperimental() bool { return r.hasLabel(ExperimentalLabel) } // IsPRBase returns true if the run is labelled experimental. func (r TestRun) IsPRBase() bool { return r.hasLabel(PRBaseLabel) } func (r TestRun) hasLabel(label string) bool { return StringSliceContains(r.Labels, label) } // Channel return the channel label, if any, for the given run. func (r TestRun) Channel() string { for _, label := range r.Labels { switch label { case StableLabel, BetaLabel, ExperimentalLabel: return label } } return "" } // Load is part of the datastore.PropertyLoadSaver interface. // We use it to reset all time to UTC and trim their monotonic clock. func (r *TestRun) Load(ps []datastore.Property) error { if err := datastore.LoadStruct(r, ps); err != nil { return err } r.CreatedAt = r.CreatedAt.UTC().Round(0) r.TimeStart = r.TimeStart.UTC().Round(0) r.TimeEnd = r.TimeEnd.UTC().Round(0) return nil } // Save is part of the datastore.PropertyLoadSaver interface. // Delegate to the default behaviour. func (r *TestRun) Save() ([]datastore.Property, error) { return datastore.SaveStruct(r) } // PendingTestRunStage represents the stage of a test run in its life cycle. type PendingTestRunStage int // Constant enums for PendingTestRunStage const ( StageGitHubQueued PendingTestRunStage = 100 StageGitHubInProgress PendingTestRunStage = 200 StageCIRunning PendingTestRunStage = 300 StageCIFinished PendingTestRunStage = 400 StageGitHubSuccess PendingTestRunStage = 500 StageGitHubFailure PendingTestRunStage = 550 StageWptFyiReceived PendingTestRunStage = 600 StageWptFyiProcessing PendingTestRunStage = 700 StageValid PendingTestRunStage = 800 StageInvalid PendingTestRunStage = 850 StageEmpty PendingTestRunStage = 851 StageDuplicate PendingTestRunStage = 852 ) func (s PendingTestRunStage) String() string { switch s { case StageGitHubQueued: return "GITHUB_QUEUED" case StageGitHubInProgress: return "GITHUB_IN_PROGRESS" case StageCIRunning: return "CI_RUNNING" case StageCIFinished: return "CI_FINISHED" case StageGitHubSuccess: return "GITHUB_SUCCESS" case StageGitHubFailure: return "GITHUB_FAILURE" case StageWptFyiReceived: return "WPTFYI_RECEIVED" case StageWptFyiProcessing: return "WPTFYI_PROCESSING" case StageValid: return "VALID" case StageInvalid: return "INVALID" case StageEmpty: return "EMPTY" case StageDuplicate: return "DUPLICATE" } return "" } // MarshalJSON is the custom JSON marshaler for PendingTestRunStage. func (s PendingTestRunStage) MarshalJSON() ([]byte, error) { return json.Marshal(s.String()) } // UnmarshalJSON is the custom JSON unmarshaler for PendingTestRunStage. func (s *PendingTestRunStage) UnmarshalJSON(b []byte) error { var str string if err := json.Unmarshal(b, &str); err != nil { return err } switch str { case "GITHUB_QUEUED": *s = StageGitHubQueued case "GITHUB_IN_PROGRESS": *s = StageGitHubInProgress case "CI_RUNNING": *s = StageCIRunning case "CI_FINISHED": *s = StageCIFinished case "GITHUB_SUCCESS": *s = StageGitHubSuccess case "GITHUB_FAILURE": *s = StageGitHubFailure case "WPTFYI_RECEIVED": *s = StageWptFyiReceived case "WPTFYI_PROCESSING": *s = StageWptFyiProcessing case "VALID": *s = StageValid case "INVALID": *s = StageInvalid case "EMPTY": *s = StageEmpty case "DUPLICATE": *s = StageDuplicate default: return fmt.Errorf("unknown stage: %s", str) } if s.String() != str { return fmt.Errorf("enum conversion error: %s != %s", s.String(), str) } return nil } // PendingTestRun represents a TestRun that has started, but is not yet // completed. type PendingTestRun struct { ID int64 `json:"id" datastore:"-"` ProductAtRevision CheckRunID int64 `json:"check_run_id" datastore:",omitempty"` Uploader string `json:"uploader"` Error string `json:"error" datastore:",omitempty"` Stage PendingTestRunStage `json:"stage"` Created time.Time `json:"created"` Updated time.Time `json:"updated"` } // Transition sets Stage to next if the transition is allowed; otherwise an // error is returned. func (s *PendingTestRun) Transition(next PendingTestRunStage) error { if next == 0 || s.Stage > next { return fmt.Errorf("cannot transition from %s to %s", s.Stage.String(), next.String()) } s.Stage = next return nil } // Load is part of the datastore.PropertyLoadSaver interface. // We use it to reset all time to UTC and trim their monotonic clock. func (s *PendingTestRun) Load(ps []datastore.Property) error { if err := datastore.LoadStruct(s, ps); err != nil { return err } s.Created = s.Created.UTC().Round(0) s.Updated = s.Updated.UTC().Round(0) return nil } // Save is part of the datastore.PropertyLoadSaver interface. // Delegate to the default behaviour. func (s *PendingTestRun) Save() ([]datastore.Property, error) { return datastore.SaveStruct(s) } // PendingTestRunByUpdated sorts the pending test runs by updated (asc) type PendingTestRunByUpdated []PendingTestRun func (a PendingTestRunByUpdated) Len() int { return len(a) } func (a PendingTestRunByUpdated) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a PendingTestRunByUpdated) Less(i, j int) bool { return a[i].Updated.Before(a[j].Updated) } type DevData struct { RunID string Date string TestName string SubtestName string Status string } type MetaData struct { Browser string BrowserVersion string } type TestHistoryEntry struct { DevData MetaData } // CheckSuite entities represent a GitHub check request that has been noted by // wpt.fyi, and will cause creation of a completed check_run when results arrive // for the PR. type CheckSuite struct { // SHA of the revision that requested a check suite. SHA string `json:"sha"` // The GitHub app ID for the custom wpt.fyi check. AppID int64 `json:"app_id"` // The GitHub app installation ID for custom wpt.fyi check InstallationID int64 `json:"installation"` Owner string `json:"owner"` // Owner username Repo string `json:"repo"` PRNumbers []int `json:"pr_numbers"` } // LabelsSet creates a set from the run's labels. func (r TestRun) LabelsSet() mapset.Set { runLabels := mapset.NewSet() for _, label := range r.Labels { runLabels.Add(label) } return runLabels } // TestRuns is a helper type for an array of TestRun entities. type TestRuns []TestRun func (t TestRuns) Len() int { return len(t) } func (t TestRuns) Less(i, j int) bool { return t[i].TimeStart.Before(t[j].TimeStart) } func (t TestRuns) Swap(i, j int) { t[i], t[j] = t[j], t[i] } // SetTestRunIDs sets the ID field for each run, from the given ids. func (t TestRuns) SetTestRunIDs(ids TestRunIDs) { for i := 0; i < len(ids) && i < len(t); i++ { t[i].ID = ids[i] } } // GetTestRunIDs gets an array of the IDs for the TestRun entities in the array. func (t TestRuns) GetTestRunIDs() TestRunIDs { ids := make([]int64, len(t)) for i, run := range t { ids[i] = run.ID } return ids } // OldestRunTimeStart returns the TimeStart of the oldest run in the set. func (t TestRuns) OldestRunTimeStart() time.Time { if len(t) < 1 { return time.Time{} } oldest := time.Now() for _, run := range t { if run.TimeStart.Before(oldest) { oldest = run.TimeStart } } return oldest } // ProductTestRuns is a tuple of a product and test runs loaded for it. type ProductTestRuns struct { Product ProductSpec TestRuns TestRuns } // TestRunsByProduct is an array of tuples of {product, matching runs}, returned // when a TestRun query is executed. type TestRunsByProduct []ProductTestRuns // AllRuns returns an array of all the loaded runs. func (t TestRunsByProduct) AllRuns() TestRuns { var runs TestRuns for _, p := range t { runs = append(runs, p.TestRuns...) } return runs } // First returns the first TestRun func (t TestRunsByProduct) First() *TestRun { all := t.AllRuns() if len(all) > 0 { return &all[0] } return nil } // ProductTestRunKeys is a tuple of a product and test run keys loaded for it. type ProductTestRunKeys struct { Product ProductSpec Keys []Key } // KeysByProduct is an array of tuples of {product, matching keys}, returned // when a TestRun key query is executed. type KeysByProduct []ProductTestRunKeys // AllKeys returns an array of all the loaded keys. func (t KeysByProduct) AllKeys() []Key { var keys []Key for _, v := range t { keys = append(keys, v.Keys...) } return keys } // TestRunIDs is a helper for an array of TestRun IDs. type TestRunIDs []int64 // GetTestRunIDs extracts the TestRunIDs from loaded datastore keys. func GetTestRunIDs(keys []Key) TestRunIDs { result := make(TestRunIDs, len(keys)) for i := range keys { result[i] = keys[i].IntID() } return result } // GetKeys returns a slice of keys for the TestRunIDs in the given datastore. func (ids TestRunIDs) GetKeys(store Datastore) []Key { keys := make([]Key, len(ids)) for i := range ids { keys[i] = store.NewIDKey("TestRun", ids[i]) } return keys } // LoadTestRuns is a helper for fetching the TestRuns from the datastore, // for the gives TestRunIDs. func (ids TestRunIDs) LoadTestRuns(store Datastore) (testRuns TestRuns, err error) { if len(ids) > 0 { keys := ids.GetKeys(store) testRuns = make(TestRuns, len(keys)) if err = store.GetMulti(keys, testRuns); err != nil { return testRuns, err } testRuns.SetTestRunIDs(ids) } return testRuns, err } // Browser holds objects that appear in browsers.json type Browser struct { InitiallyLoaded bool `json:"initially_loaded"` CurrentlyRun bool `json:"currently_run"` BrowserName string `json:"browser_name"` BrowserVersion string `json:"browser_version"` OSName string `json:"os_name"` OSVersion string `json:"os_version"` Sauce bool `json:"sauce"` } // Token is used for test result uploads. type Token struct { Secret string `json:"secret"` } // Uploader is a username/password combo accepted by // the results receiver. type Uploader struct { Username string Password string } // Flag represents an enviroment feature flag's default state. type Flag struct { Name string `datastore:"-"` // Name is the key in datastore. Enabled bool } // LegacySearchRunResult is the results data from legacy test summarys. These // summaries contain a "pass count" and a "total count", where the test itself // counts as 1, and each subtest counts as 1. The "pass count" contains any // status values that are "PASS" or "OK". type LegacySearchRunResult struct { // Passes is the number of test results in a PASS/OK state. Passes int `json:"passes"` // Total is the total number of test results for this run/file pair. Total int `json:"total"` // Status represents either the test status or harness status. // This will be an empty string for old summaries. Status string `json:"status"` // NewAggProcess represents whether the summary was created with the old // or new aggregation process. NewAggProcess bool `json:"newAggProcess"` } // SearchResult contains data regarding a particular test file over a collection // of runs. The runs are identified externally in a parallel slice (see // SearchResponse). type SearchResult struct { // Test is the name of a test; this often corresponds to a test file path in // the WPT source reposiory. Test string `json:"test"` // LegacyStatus is the results data from legacy test summaries. These // summaries contain a "pass count" and a "total count", where the test itself // counts as 1, and each subtest counts as 1. The "pass count" contains any // status values that are "PASS" or "OK". LegacyStatus []LegacySearchRunResult `json:"legacy_status,omitempty"` // Interoperability scores. For N browsers, we have an array of // N+1 items, where the index X is the number of items passing in exactly // X of the N browsers. e.g. for 4 browsers, [0/4, 1/4, 2/4, 3/4, 4/4]. Interop []int `json:"interop,omitempty"` // Subtests (names) which are included in the LegacyStatus summary. Subtests []string `json:"subtests,omitempty"` // Diff count of subtests which are included in the LegacyStatus summary. Diff TestDiff `json:"diff,omitempty"` } // SearchResponse contains a response to search API calls, including specific // runs whose results were searched and the search results themselves. type SearchResponse struct { // Runs is the specific runs for which results were retrieved. Each run, in // order, corresponds to a Status entry in each SearchResult in Results. Runs []TestRun `json:"runs"` // IgnoredRuns is any runs that the client requested to be included in the // query, but were not included. This optional field may be non-nil if, for // example, results are being served from an incompelte cache of runs and some // runs described in the query request are not resident in the cache. IgnoredRuns []TestRun `json:"ignored_runs,omitempty"` // Results is the collection of test results, grouped by test file name. Results []SearchResult `json:"results"` // MetadataResponse is a response to a wpt-metadata query. MetadataResponse MetadataResults `json:"metadata,omitempty"` }
Editor is loading...