github.com/web-platform-tests/wpt.fyi@v0.0.0-20240530210107-70cf978996f1/util/populate_dev_data.go (about)

     1  // Copyright 2017 The WPT Dashboard Project. All rights reserved.
     2  // Use of this source code is governed by a BSD-style license that can be
     3  // found in the LICENSE file.
     4  
     5  package main
     6  
     7  import (
     8  	"context"
     9  	"flag"
    10  	"fmt"
    11  	"log"
    12  	"os"
    13  	"strings"
    14  	"time"
    15  
    16  	mapset "github.com/deckarep/golang-set"
    17  
    18  	"github.com/web-platform-tests/wpt.fyi/shared"
    19  	"github.com/web-platform-tests/wpt.fyi/shared/metrics"
    20  )
    21  
    22  var (
    23  	project        = flag.String("project", "", "project ID used to connect to Datastore")
    24  	datastoreHost  = flag.String("datastore_host", "", "Cloud Datastore emulator host")
    25  	localHost      = flag.String("local_host", "localhost:8080", "local dev_appserver.py webapp host")
    26  	remoteHost     = flag.String("remote_host", "wpt.fyi", "wpt.fyi host to fetch prod runs from")
    27  	numRemoteRuns  = flag.Int("num_remote_runs", 10, "number of remote runs to copy from host to local environment")
    28  	staticRuns     = flag.Bool("static_runs", false, "Include runs in the /static dir")
    29  	remoteRuns     = flag.Bool("remote_runs", true, "Include copies of remote runs")
    30  	seenTestRunIDs = mapset.NewSet()
    31  	labels         = flag.String("labels", "", "Labels for which to fetch runs")
    32  )
    33  
    34  // populate_dev_data.go populates a local running webapp instance with some
    35  // of the latest production entities, so that there's data to view.
    36  //
    37  // Usage (from util/):
    38  // go run populate_dev_data.go
    39  func main() {
    40  	log.SetFlags(log.LstdFlags | log.Lshortfile)
    41  	flag.Parse()
    42  
    43  	if *project != "" {
    44  		os.Setenv("DATASTORE_PROJECT_ID", *project)
    45  	}
    46  	if *datastoreHost != "" {
    47  		os.Setenv("DATASTORE_EMULATOR_HOST", *datastoreHost)
    48  	}
    49  
    50  	ctx := context.Background()
    51  	shared.Clients.Init(ctx)
    52  	defer shared.Clients.Close()
    53  
    54  	log.Printf("Adding dev data to local emulator...")
    55  
    56  	emptySecretToken := &shared.Token{}
    57  	enabledFlag := &shared.Flag{Enabled: true}
    58  	staticDataTime := time.Now()
    59  
    60  	// Follow pattern established in run/*.py data collection code.
    61  	const staticRunSHA = "24278ab61781de72ed363b866ae6b50b86822b27"
    62  	summaryURLFmtString := "http://%s/static/%s/%s"
    63  	chrome := shared.TestRun{
    64  		ProductAtRevision: shared.ProductAtRevision{
    65  			Product: shared.Product{
    66  				BrowserName:    "chrome",
    67  				BrowserVersion: "74.0",
    68  				OSName:         "linux",
    69  				OSVersion:      "3.16",
    70  			},
    71  			FullRevisionHash: staticRunSHA,
    72  			Revision:         staticRunSHA[:10],
    73  		},
    74  		ResultsURL: fmt.Sprintf(summaryURLFmtString, *localHost, staticRunSHA[:10], "chrome[stable]-summary_v2.json.gz"),
    75  		CreatedAt:  staticDataTime,
    76  		TimeStart:  staticDataTime,
    77  		Labels:     []string{"chrome", shared.StableLabel},
    78  	}
    79  	chromeExp := chrome
    80  	chromeExp.BrowserVersion = "76.0"
    81  	chromeExp.Labels = []string{"chrome", shared.ExperimentalLabel}
    82  	chromeExp.ResultsURL = strings.Replace(chrome.ResultsURL, "[stable]", "[experimental]", -1)
    83  
    84  	edge := chrome
    85  	edge.BrowserName = "edge"
    86  	edge.BrowserVersion = "18"
    87  	edge.OSName = "windows"
    88  	edge.OSVersion = "10"
    89  	edge.ResultsURL = fmt.Sprintf(summaryURLFmtString, *localHost, staticRunSHA[:10], "edge[stable]-summary_v2.json.gz")
    90  	edge.Labels = []string{"edge", shared.StableLabel}
    91  
    92  	edgeExp := edge
    93  	edgeExp.BrowserVersion = "20"
    94  	edgeExp.ResultsURL = strings.Replace(edge.ResultsURL, "[stable]", "[experimental]", -1)
    95  	edgeExp.Labels = []string{"edge", shared.ExperimentalLabel}
    96  
    97  	firefox := chrome
    98  	firefox.BrowserName = "firefox"
    99  	firefox.BrowserVersion = "66"
   100  	firefox.ResultsURL = fmt.Sprintf(summaryURLFmtString, *localHost, staticRunSHA[:10], "firefox[stable]-summary_v2.json.gz")
   101  	firefox.Labels = []string{"firefox", shared.StableLabel}
   102  	firefoxExp := firefox
   103  	firefoxExp.BrowserVersion = "68.0"
   104  	firefoxExp.Labels = []string{"firefox", shared.ExperimentalLabel}
   105  	firefoxExp.ResultsURL = strings.Replace(firefox.ResultsURL, "[stable]", "[experimental]", -1)
   106  
   107  	safari := chrome
   108  	safari.BrowserName = "safari"
   109  	safari.BrowserVersion = "12.1"
   110  	safari.OSName = "mac"
   111  	safari.OSName = "10.13"
   112  	safari.ResultsURL = fmt.Sprintf(summaryURLFmtString, *localHost, staticRunSHA[:10], "safari[stable]-summary_v2.json.gz")
   113  	safari.Labels = []string{"safari", shared.StableLabel}
   114  	safariExp := safari
   115  	safariExp.BrowserVersion = "81 preview"
   116  	safariExp.Labels = []string{"safari", shared.ExperimentalLabel}
   117  	safariExp.ResultsURL = strings.Replace(safari.ResultsURL, "[stable]", "[experimental]", -1)
   118  
   119  	staticTestRuns := shared.TestRuns{
   120  		chrome,
   121  		chromeExp,
   122  		edge,
   123  		edgeExp,
   124  		firefox,
   125  		firefoxExp,
   126  		safari,
   127  		safariExp,
   128  	}
   129  	labelRuns(staticTestRuns, "test", "static", shared.MasterLabel)
   130  
   131  	timeZero := time.Unix(0, 0)
   132  	// Follow pattern established in metrics/run/*.go data collection code.
   133  	// Use unzipped JSON for local dev.
   134  	const metricsURLFmtString = "/static/wptd-metrics/0-0/%s.json"
   135  	staticTestRunMetadata := make([]interface{}, len(staticTestRuns))
   136  	for i := range staticTestRuns {
   137  		staticTestRunMetadata[i] = &staticTestRuns[i]
   138  	}
   139  	passRateMetadata := metrics.PassRateMetadata{
   140  		TestRunsMetadata: metrics.TestRunsMetadata{
   141  			StartTime: timeZero,
   142  			EndTime:   timeZero,
   143  			DataURL:   fmt.Sprintf(metricsURLFmtString, "pass-rates"),
   144  		},
   145  	}
   146  
   147  	testRunKindName := "TestRun"
   148  	passRateMetadataKindName := metrics.GetDatastoreKindName(
   149  		metrics.PassRateMetadata{})
   150  
   151  	log.Print("Adding local (empty) secrets...")
   152  	store := shared.NewAppEngineDatastore(ctx, false)
   153  	addSecretToken(store, "upload-token", emptySecretToken)
   154  	addSecretToken(store, "github-wpt-fyi-bot-token", emptySecretToken)
   155  	addSecretToken(store, "github-oauth-client-id", emptySecretToken)
   156  	addSecretToken(store, "github-oauth-client-secret", emptySecretToken)
   157  	addSecretToken(store, "secure-cookie-hashkey", &shared.Token{
   158  		Secret: "a-very-secret-sixty-four-bytes!!a-very-secret-sixty-four-bytes!!",
   159  	})
   160  	addSecretToken(store, "secure-cookie-blockkey", &shared.Token{
   161  		Secret: "a-very-secret-thirty-two-bytes!!",
   162  	})
   163  
   164  	log.Print("Adding flag defaults...")
   165  	addFlag(store, "queryBuilder", enabledFlag)
   166  	addFlag(store, "diffFilter", enabledFlag)
   167  	addFlag(store, "diffFromAPI", enabledFlag)
   168  	addFlag(store, "structuredQueries", enabledFlag)
   169  	addFlag(store, "diffRenames", enabledFlag)
   170  	addFlag(store, "paginationTokens", enabledFlag)
   171  
   172  	log.Print("Adding uploader \"test\"...")
   173  	addData(store, "Uploader", []interface{}{
   174  		&shared.Uploader{Username: "test", Password: "123"},
   175  	})
   176  
   177  	if *staticRuns {
   178  		log.Print("Adding local mock data (static/)...")
   179  		for i, key := range addData(store, testRunKindName, staticTestRunMetadata) {
   180  			staticTestRuns[i].ID = key.IntID()
   181  		}
   182  		stableRuns := shared.TestRuns{}
   183  		defaultRuns := shared.TestRuns{}
   184  		for _, run := range staticTestRuns {
   185  			labels := run.LabelsSet()
   186  			if labels.Contains(shared.StableLabel) {
   187  				stableRuns = append(stableRuns, run)
   188  			} else if labels.Contains("edge") || labels.Contains(shared.ExperimentalLabel) {
   189  				defaultRuns = append(defaultRuns, run)
   190  			}
   191  		}
   192  		stableInterop := passRateMetadata
   193  		stableInterop.TestRunIDs = stableRuns.GetTestRunIDs()
   194  		defaultInterop := passRateMetadata
   195  		defaultInterop.TestRunIDs = defaultRuns.GetTestRunIDs()
   196  		addData(store, passRateMetadataKindName, []interface{}{
   197  			&stableInterop,
   198  			&defaultInterop,
   199  		})
   200  	}
   201  
   202  	if *remoteRuns {
   203  		log.Print("Adding latest production TestRun data...")
   204  		extraLabels := mapset.NewSet()
   205  		if labels != nil {
   206  			for _, s := range strings.Split(*labels, ",") {
   207  				if s != "" {
   208  					extraLabels.Add(s)
   209  				}
   210  			}
   211  		}
   212  		filters := shared.TestRunFilter{
   213  			Labels:   extraLabels.Union(mapset.NewSetWith(shared.StableLabel)),
   214  			MaxCount: numRemoteRuns,
   215  		}
   216  		copyProdRuns(store, filters)
   217  
   218  		log.Print("Adding latest master TestRun data...")
   219  		filters.Labels = extraLabels.Union(mapset.NewSetWith(shared.MasterLabel))
   220  		copyProdRuns(store, filters)
   221  
   222  		log.Print("Adding latest experimental TestRun data...")
   223  		filters.Labels = extraLabels.Union(mapset.NewSetWith(shared.ExperimentalLabel))
   224  		copyProdRuns(store, filters)
   225  
   226  		log.Print("Adding latest beta TestRun data...")
   227  		filters.Labels = extraLabels.Union(mapset.NewSetWith(shared.BetaLabel))
   228  		copyProdRuns(store, filters)
   229  
   230  		log.Print("Adding latest aligned Edge stable and Chrome/Firefox/Safari experimental data...")
   231  		filters.Labels = extraLabels.Union(mapset.NewSet(shared.MasterLabel))
   232  		filters.Products, _ = shared.ParseProductSpecs("chrome[experimental]", "edge[stable]", "firefox[experimental]", "safari[experimental]")
   233  		copyProdRuns(store, filters)
   234  
   235  		log.Printf("Successfully copied a total of %v distinct TestRuns", seenTestRunIDs.Cardinality())
   236  
   237  		log.Print("Adding latest production PendingTestRun...")
   238  		copyProdPendingRuns(store, *numRemoteRuns)
   239  	}
   240  
   241  	log.Print("Adding test history data...")
   242  	addFakeHistoryData(store)
   243  }
   244  
   245  func copyProdRuns(store shared.Datastore, filters shared.TestRunFilter) {
   246  	for _, aligned := range []bool{false, true} {
   247  		if aligned {
   248  			filters.Aligned = &aligned
   249  		}
   250  		prodTestRuns, err := shared.FetchRuns(*remoteHost, filters)
   251  		if err != nil {
   252  			log.Print(err)
   253  			continue
   254  		}
   255  		labelRuns(prodTestRuns, "prod")
   256  
   257  		latestProductionTestRunMetadata := make([]interface{}, 0, len(prodTestRuns))
   258  		for i := range prodTestRuns {
   259  			if !seenTestRunIDs.Contains(prodTestRuns[i].ID) {
   260  				seenTestRunIDs.Add(prodTestRuns[i].ID)
   261  				latestProductionTestRunMetadata = append(latestProductionTestRunMetadata, &prodTestRuns[i])
   262  			}
   263  		}
   264  		addData(store, "TestRun", latestProductionTestRunMetadata)
   265  	}
   266  }
   267  
   268  func copyProdPendingRuns(store shared.Datastore, numRuns int) {
   269  	pendingRuns, err := FetchPendingRuns(*remoteHost)
   270  	if err != nil {
   271  		log.Fatalf("Failed to fetch pending runs: %s", err.Error())
   272  	}
   273  	var castRuns []interface{}
   274  	for i := range pendingRuns {
   275  		castRuns = append(castRuns, &pendingRuns[i])
   276  	}
   277  	addData(store, "PendingTestRun", castRuns)
   278  }
   279  
   280  func labelRuns(runs []shared.TestRun, labels ...string) {
   281  	for i := range runs {
   282  		for _, label := range labels {
   283  			runs[i].Labels = append(runs[i].Labels, label)
   284  		}
   285  	}
   286  }
   287  
   288  func addSecretToken(store shared.Datastore, id string, data interface{}) {
   289  	key := store.NewNameKey("Token", id)
   290  	if _, err := store.Put(key, data); err != nil {
   291  		log.Fatalf("Failed to add %s secret: %s", id, err.Error())
   292  	}
   293  	log.Printf("Added %s secret", id)
   294  }
   295  
   296  func addFlag(store shared.Datastore, id string, data interface{}) {
   297  	key := store.NewNameKey("Flag", id)
   298  	if _, err := store.Put(key, data); err != nil {
   299  		log.Fatalf("Failed to add %s flag: %s", id, err.Error())
   300  	}
   301  	log.Printf("Added %s flag", id)
   302  }
   303  
   304  func addData(store shared.Datastore, kindName string, data []interface{}) (keys []shared.Key) {
   305  	keys = make([]shared.Key, len(data))
   306  	for i := range data {
   307  		keys[i] = store.NewIncompleteKey(kindName)
   308  	}
   309  	var err error
   310  	if keys, err = store.PutMulti(keys, data); err != nil {
   311  		log.Fatalf("Failed to add %s entities: %s", kindName, err.Error())
   312  	}
   313  	log.Printf("Added %v %s entities", len(data), kindName)
   314  	return keys
   315  }
   316  
   317  // FetchPendingRuns fetches recent PendingTestRuns.
   318  func FetchPendingRuns(wptdHost string) ([]shared.PendingTestRun, error) {
   319  	url := "https://" + wptdHost + "/api/status"
   320  	var pendingRuns []shared.PendingTestRun
   321  	err := shared.FetchJSON(url, &pendingRuns)
   322  	return pendingRuns, err
   323  }
   324  
   325  // TODO: Import real data here when staging is populated with real data
   326  func addFakeHistoryData(store shared.Datastore) {
   327  	// browser_name,browser_version,date,test_name,subtest_name,status
   328  	devData := []map[string]string{
   329  		{
   330  			"run_id":       "5074677897101312",
   331  			"date":         "2022-06-02T06:02:55.000Z",
   332  			"test_name":    "example test name",
   333  			"subtest_name": "",
   334  			"status":       "OK",
   335  		},
   336  		{
   337  			"run_id":       "5074677897101312",
   338  			"date":         "2023-02-21T03:08:15.000Z",
   339  			"test_name":    "example test name",
   340  			"subtest_name": "",
   341  			"status":       "TIMEOUT",
   342  		},
   343  		{
   344  			"run_id":       "5074677897101312",
   345  			"date":         "2023-03-30T20:27:32.000Z",
   346  			"test_name":    "example test name",
   347  			"subtest_name": "",
   348  			"status":       "OK",
   349  		},
   350  		{
   351  			"run_id":       "5074677897101312",
   352  			"date":         "2022-06-02T06:02:55.000Z",
   353  			"test_name":    "example test name",
   354  			"subtest_name": "subtest_name_1",
   355  			"status":       "PASS",
   356  		},
   357  		{
   358  			"run_id":       "5074677897101312",
   359  			"date":         "2022-08-14T06:02:55.000Z",
   360  			"test_name":    "example test name",
   361  			"subtest_name": "subtest_name_1",
   362  			"status":       "FAIL",
   363  		},
   364  		{
   365  			"run_id":       "5074677897101312",
   366  			"date":         "2023-02-21T03:08:15.000Z",
   367  			"test_name":    "example test name",
   368  			"subtest_name": "subtest_name_1",
   369  			"status":       "NOTRUN",
   370  		},
   371  		{
   372  			"run_id":       "5074677897101312",
   373  			"date":         "2023-03-30T20:27:32.611Z",
   374  			"test_name":    "example test name",
   375  			"subtest_name": "subtest_name_1",
   376  			"status":       "FAIL",
   377  		},
   378  		{
   379  			"run_id":       "5074677897101312",
   380  			"date":         "2023-06-19T20:54:12.611Z",
   381  			"test_name":    "example test name",
   382  			"subtest_name": "subtest_name_1",
   383  			"status":       "PASS",
   384  		},
   385  		{
   386  			"run_id":       "5074677897101312",
   387  			"date":         "2022-06-02T06:02:55.000Z",
   388  			"test_name":    "example test name",
   389  			"subtest_name": "subtest_name_2",
   390  			"status":       "TIMEOUT",
   391  		},
   392  		{
   393  			"run_id":       "5074677897101312",
   394  			"date":         "2022-09-25T23:49:35.000Z",
   395  			"test_name":    "example test name",
   396  			"subtest_name": "subtest_name_2",
   397  			"status":       "PASS",
   398  		},
   399  		{
   400  			"run_id":       "5074677897101312",
   401  			"date":         "2023-02-21T03:08:15.000Z",
   402  			"test_name":    "example test name",
   403  			"subtest_name": "subtest_name_2",
   404  			"status":       "NOTRUN",
   405  		},
   406  		{
   407  			"run_id":       "5074677897101312",
   408  			"date":         "2023-03-30T20:27:32.611Z",
   409  			"test_name":    "example test name",
   410  			"subtest_name": "subtest_name_2",
   411  			"status":       "PASS",
   412  		},
   413  		{
   414  			"run_id":       "5074677897101312",
   415  			"date":         "2022-06-02T06:02:55.000Z",
   416  			"test_name":    "example test name",
   417  			"subtest_name": "subtest_name_3",
   418  			"status":       "PASS",
   419  		},
   420  		{
   421  			"run_id":       "5074677897101312",
   422  			"date":         "2023-02-21T03:08:15.000Z",
   423  			"test_name":    "example test name",
   424  			"subtest_name": "subtest_name_3",
   425  			"status":       "NOTRUN",
   426  		},
   427  		{
   428  			"run_id":       "5074677897101312",
   429  			"date":         "2023-03-30T20:27:32.611Z",
   430  			"test_name":    "example test name",
   431  			"subtest_name": "subtest_name_3",
   432  			"status":       "PASS",
   433  		},
   434  	}
   435  
   436  	browserMetadata := []map[string]string{
   437  		{
   438  			"browser": "chrome",
   439  		},
   440  		{
   441  			"browser": "edge",
   442  		},
   443  
   444  		{
   445  			"browser": "firefox",
   446  		},
   447  
   448  		{
   449  			"browser": "safari",
   450  		},
   451  	}
   452  
   453  	browserEntries := make([]interface{}, 0, len(devData))
   454  	for _, metadata := range browserMetadata {
   455  		for _, entry := range devData {
   456  			testHistoryEntry := shared.TestHistoryEntry{
   457  				BrowserName: metadata["browser"],
   458  				RunID:       entry["run_id"],
   459  				Date:        entry["date"],
   460  				TestName:    entry["test_name"],
   461  				SubtestName: entry["subtest_name"],
   462  				Status:      entry["status"],
   463  			}
   464  			browserEntries = append(browserEntries, &testHistoryEntry)
   465  		}
   466  	}
   467  	addData(store, "TestHistoryEntry", browserEntries)
   468  }