models.go
unknown
plain_text
2 years ago
17 kB
6
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...