1cb93a386Sopenharmony_ci// Copyright 2018 The Chromium Authors. All rights reserved. 2cb93a386Sopenharmony_ci// Use of this source code is governed by a BSD-style license that can be 3cb93a386Sopenharmony_ci// found in the LICENSE file. 4cb93a386Sopenharmony_ci 5cb93a386Sopenharmony_cipackage main 6cb93a386Sopenharmony_ci 7cb93a386Sopenharmony_ci// This server runs along side the karma tests and listens for POST requests 8cb93a386Sopenharmony_ci// when any test case reports it has output for Gold. See testReporter.js 9cb93a386Sopenharmony_ci// for the browser side part. 10cb93a386Sopenharmony_ci 11cb93a386Sopenharmony_ciimport ( 12cb93a386Sopenharmony_ci "bytes" 13cb93a386Sopenharmony_ci "crypto/md5" 14cb93a386Sopenharmony_ci "encoding/base64" 15cb93a386Sopenharmony_ci "encoding/json" 16cb93a386Sopenharmony_ci "flag" 17cb93a386Sopenharmony_ci "fmt" 18cb93a386Sopenharmony_ci "image" 19cb93a386Sopenharmony_ci "image/png" 20cb93a386Sopenharmony_ci "io/ioutil" 21cb93a386Sopenharmony_ci "log" 22cb93a386Sopenharmony_ci "net/http" 23cb93a386Sopenharmony_ci "os" 24cb93a386Sopenharmony_ci "path" 25cb93a386Sopenharmony_ci "strings" 26cb93a386Sopenharmony_ci "sync" 27cb93a386Sopenharmony_ci 28cb93a386Sopenharmony_ci "go.skia.org/infra/go/util" 29cb93a386Sopenharmony_ci "go.skia.org/infra/golden/go/jsonio" 30cb93a386Sopenharmony_ci "go.skia.org/infra/golden/go/types" 31cb93a386Sopenharmony_ci) 32cb93a386Sopenharmony_ci 33cb93a386Sopenharmony_ci// This allows us to use upload_dm_results.py out of the box 34cb93a386Sopenharmony_ciconst JSON_FILENAME = "dm.json" 35cb93a386Sopenharmony_ci 36cb93a386Sopenharmony_civar ( 37cb93a386Sopenharmony_ci outDir = flag.String("out_dir", "/OUT/", "location to dump the Gold JSON and pngs") 38cb93a386Sopenharmony_ci port = flag.String("port", "8081", "Port to listen on.") 39cb93a386Sopenharmony_ci 40cb93a386Sopenharmony_ci browser = flag.String("browser", "Chrome", "Browser Key") 41cb93a386Sopenharmony_ci buildBucketID = flag.String("buildbucket_build_id", "", "Buildbucket build id key") 42cb93a386Sopenharmony_ci builder = flag.String("builder", "", "Builder, like 'Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-PathKit'") 43cb93a386Sopenharmony_ci compiledLanguage = flag.String("compiled_language", "wasm", "wasm or asm.js") 44cb93a386Sopenharmony_ci config = flag.String("config", "Release", "Configuration (e.g. Debug/Release) key") 45cb93a386Sopenharmony_ci gitHash = flag.String("git_hash", "-", "The git commit hash of the version being tested") 46cb93a386Sopenharmony_ci hostOS = flag.String("host_os", "Debian9", "OS Key") 47cb93a386Sopenharmony_ci issue = flag.String("issue", "", "ChangelistID (if tryjob)") 48cb93a386Sopenharmony_ci patchset = flag.Int("patchset", 0, "patchset (if tryjob)") 49cb93a386Sopenharmony_ci sourceType = flag.String("source_type", "pathkit", "Gold Source type, like pathkit,canvaskit") 50cb93a386Sopenharmony_ci) 51cb93a386Sopenharmony_ci 52cb93a386Sopenharmony_ci// Received from the JS side. 53cb93a386Sopenharmony_citype reportBody struct { 54cb93a386Sopenharmony_ci // e.g. "canvas" or "svg" 55cb93a386Sopenharmony_ci OutputType string `json:"output_type"` 56cb93a386Sopenharmony_ci // a base64 encoded PNG image. 57cb93a386Sopenharmony_ci Data string `json:"data"` 58cb93a386Sopenharmony_ci // a name describing the test. Should be unique enough to allow use of grep. 59cb93a386Sopenharmony_ci TestName string `json:"test_name"` 60cb93a386Sopenharmony_ci} 61cb93a386Sopenharmony_ci 62cb93a386Sopenharmony_ci// The keys to be used at the top level for all Results. 63cb93a386Sopenharmony_civar defaultKeys map[string]string 64cb93a386Sopenharmony_ci 65cb93a386Sopenharmony_ci// contains all the results reported in through report_gold_data 66cb93a386Sopenharmony_civar results []jsonio.Result 67cb93a386Sopenharmony_civar resultsMutex sync.Mutex 68cb93a386Sopenharmony_ci 69cb93a386Sopenharmony_cifunc main() { 70cb93a386Sopenharmony_ci flag.Parse() 71cb93a386Sopenharmony_ci 72cb93a386Sopenharmony_ci cpuGPU := "CPU" 73cb93a386Sopenharmony_ci if strings.Index(*builder, "-GPU-") != -1 { 74cb93a386Sopenharmony_ci cpuGPU = "GPU" 75cb93a386Sopenharmony_ci } 76cb93a386Sopenharmony_ci defaultKeys = map[string]string{ 77cb93a386Sopenharmony_ci "arch": "WASM", 78cb93a386Sopenharmony_ci "browser": *browser, 79cb93a386Sopenharmony_ci "compiled_language": *compiledLanguage, 80cb93a386Sopenharmony_ci "compiler": "emsdk", 81cb93a386Sopenharmony_ci "configuration": *config, 82cb93a386Sopenharmony_ci "cpu_or_gpu": cpuGPU, 83cb93a386Sopenharmony_ci "cpu_or_gpu_value": "Browser", 84cb93a386Sopenharmony_ci "os": *hostOS, 85cb93a386Sopenharmony_ci "source_type": *sourceType, 86cb93a386Sopenharmony_ci } 87cb93a386Sopenharmony_ci 88cb93a386Sopenharmony_ci results = []jsonio.Result{} 89cb93a386Sopenharmony_ci 90cb93a386Sopenharmony_ci http.HandleFunc("/report_gold_data", reporter) 91cb93a386Sopenharmony_ci http.HandleFunc("/dump_json", dumpJSON) 92cb93a386Sopenharmony_ci 93cb93a386Sopenharmony_ci fmt.Printf("Waiting for gold ingestion on port %s\n", *port) 94cb93a386Sopenharmony_ci 95cb93a386Sopenharmony_ci log.Fatal(http.ListenAndServe(":"+*port, nil)) 96cb93a386Sopenharmony_ci} 97cb93a386Sopenharmony_ci 98cb93a386Sopenharmony_ci// reporter handles when the client reports a test has Gold output. 99cb93a386Sopenharmony_ci// It writes the corresponding PNG to disk and appends a Result, assuming 100cb93a386Sopenharmony_ci// no errors. 101cb93a386Sopenharmony_cifunc reporter(w http.ResponseWriter, r *http.Request) { 102cb93a386Sopenharmony_ci if r.Method != "POST" { 103cb93a386Sopenharmony_ci http.Error(w, "Only POST accepted", 400) 104cb93a386Sopenharmony_ci return 105cb93a386Sopenharmony_ci } 106cb93a386Sopenharmony_ci defer util.Close(r.Body) 107cb93a386Sopenharmony_ci 108cb93a386Sopenharmony_ci body, err := ioutil.ReadAll(r.Body) 109cb93a386Sopenharmony_ci if err != nil { 110cb93a386Sopenharmony_ci http.Error(w, "Malformed body", 400) 111cb93a386Sopenharmony_ci return 112cb93a386Sopenharmony_ci } 113cb93a386Sopenharmony_ci 114cb93a386Sopenharmony_ci testOutput := reportBody{} 115cb93a386Sopenharmony_ci if err := json.Unmarshal(body, &testOutput); err != nil { 116cb93a386Sopenharmony_ci fmt.Println(err) 117cb93a386Sopenharmony_ci http.Error(w, "Could not unmarshal JSON", 400) 118cb93a386Sopenharmony_ci return 119cb93a386Sopenharmony_ci } 120cb93a386Sopenharmony_ci 121cb93a386Sopenharmony_ci hash := "" 122cb93a386Sopenharmony_ci if hash, err = writeBase64EncodedPNG(testOutput.Data); err != nil { 123cb93a386Sopenharmony_ci fmt.Println(err) 124cb93a386Sopenharmony_ci http.Error(w, "Could not write image to disk", 500) 125cb93a386Sopenharmony_ci return 126cb93a386Sopenharmony_ci } 127cb93a386Sopenharmony_ci 128cb93a386Sopenharmony_ci if _, err := w.Write([]byte("Accepted")); err != nil { 129cb93a386Sopenharmony_ci fmt.Printf("Could not write response: %s\n", err) 130cb93a386Sopenharmony_ci return 131cb93a386Sopenharmony_ci } 132cb93a386Sopenharmony_ci 133cb93a386Sopenharmony_ci resultsMutex.Lock() 134cb93a386Sopenharmony_ci defer resultsMutex.Unlock() 135cb93a386Sopenharmony_ci results = append(results, jsonio.Result{ 136cb93a386Sopenharmony_ci Digest: types.Digest(hash), 137cb93a386Sopenharmony_ci Key: map[string]string{ 138cb93a386Sopenharmony_ci "name": testOutput.TestName, 139cb93a386Sopenharmony_ci "config": testOutput.OutputType, 140cb93a386Sopenharmony_ci }, 141cb93a386Sopenharmony_ci Options: map[string]string{ 142cb93a386Sopenharmony_ci "ext": "png", 143cb93a386Sopenharmony_ci }, 144cb93a386Sopenharmony_ci }) 145cb93a386Sopenharmony_ci} 146cb93a386Sopenharmony_ci 147cb93a386Sopenharmony_ci// createOutputFile creates a file and set permissions correctly. 148cb93a386Sopenharmony_cifunc createOutputFile(p string) (*os.File, error) { 149cb93a386Sopenharmony_ci outputFile, err := os.Create(p) 150cb93a386Sopenharmony_ci if err != nil { 151cb93a386Sopenharmony_ci return nil, fmt.Errorf("Could not open file %s on disk: %s", p, err) 152cb93a386Sopenharmony_ci } 153cb93a386Sopenharmony_ci // Make this accessible (and deletable) by all users 154cb93a386Sopenharmony_ci if err = outputFile.Chmod(0666); err != nil { 155cb93a386Sopenharmony_ci return nil, fmt.Errorf("Could not change permissions of file %s: %s", p, err) 156cb93a386Sopenharmony_ci } 157cb93a386Sopenharmony_ci return outputFile, nil 158cb93a386Sopenharmony_ci} 159cb93a386Sopenharmony_ci 160cb93a386Sopenharmony_ci// dumpJSON writes out a JSON file with all the results, typically at the end of 161cb93a386Sopenharmony_ci// all the tests. 162cb93a386Sopenharmony_cifunc dumpJSON(w http.ResponseWriter, r *http.Request) { 163cb93a386Sopenharmony_ci if r.Method != "POST" { 164cb93a386Sopenharmony_ci http.Error(w, "Only POST accepted", 400) 165cb93a386Sopenharmony_ci return 166cb93a386Sopenharmony_ci } 167cb93a386Sopenharmony_ci 168cb93a386Sopenharmony_ci p := path.Join(*outDir, JSON_FILENAME) 169cb93a386Sopenharmony_ci outputFile, err := createOutputFile(p) 170cb93a386Sopenharmony_ci if err != nil { 171cb93a386Sopenharmony_ci fmt.Println(err) 172cb93a386Sopenharmony_ci http.Error(w, "Could not open json file on disk", 500) 173cb93a386Sopenharmony_ci return 174cb93a386Sopenharmony_ci } 175cb93a386Sopenharmony_ci defer util.Close(outputFile) 176cb93a386Sopenharmony_ci 177cb93a386Sopenharmony_ci dmresults := jsonio.GoldResults{ 178cb93a386Sopenharmony_ci GitHash: *gitHash, 179cb93a386Sopenharmony_ci Key: defaultKeys, 180cb93a386Sopenharmony_ci Results: results, 181cb93a386Sopenharmony_ci } 182cb93a386Sopenharmony_ci 183cb93a386Sopenharmony_ci if *patchset > 0 { 184cb93a386Sopenharmony_ci dmresults.ChangelistID = *issue 185cb93a386Sopenharmony_ci dmresults.PatchsetOrder = *patchset 186cb93a386Sopenharmony_ci dmresults.CodeReviewSystem = "gerrit" 187cb93a386Sopenharmony_ci dmresults.ContinuousIntegrationSystem = "buildbucket" 188cb93a386Sopenharmony_ci dmresults.TryJobID = *buildBucketID 189cb93a386Sopenharmony_ci } 190cb93a386Sopenharmony_ci 191cb93a386Sopenharmony_ci enc := json.NewEncoder(outputFile) 192cb93a386Sopenharmony_ci enc.SetIndent("", " ") // Make it human readable. 193cb93a386Sopenharmony_ci if err := enc.Encode(&dmresults); err != nil { 194cb93a386Sopenharmony_ci fmt.Println(err) 195cb93a386Sopenharmony_ci http.Error(w, "Could not write json to disk", 500) 196cb93a386Sopenharmony_ci return 197cb93a386Sopenharmony_ci } 198cb93a386Sopenharmony_ci fmt.Println("JSON Written") 199cb93a386Sopenharmony_ci} 200cb93a386Sopenharmony_ci 201cb93a386Sopenharmony_ci// writeBase64EncodedPNG writes a PNG to disk and returns the md5 of the 202cb93a386Sopenharmony_ci// decoded PNG bytes and any error. This hash is what will be used as 203cb93a386Sopenharmony_ci// the gold digest and the file name. 204cb93a386Sopenharmony_cifunc writeBase64EncodedPNG(data string) (string, error) { 205cb93a386Sopenharmony_ci // data starts with something like data:image/png;base64,[data] 206cb93a386Sopenharmony_ci // https://en.wikipedia.org/wiki/Data_URI_scheme 207cb93a386Sopenharmony_ci start := strings.Index(data, ",") 208cb93a386Sopenharmony_ci b := bytes.NewBufferString(data[start+1:]) 209cb93a386Sopenharmony_ci pngReader := base64.NewDecoder(base64.StdEncoding, b) 210cb93a386Sopenharmony_ci 211cb93a386Sopenharmony_ci pngBytes, err := ioutil.ReadAll(pngReader) 212cb93a386Sopenharmony_ci if err != nil { 213cb93a386Sopenharmony_ci return "", fmt.Errorf("Could not decode base 64 encoding %s", err) 214cb93a386Sopenharmony_ci } 215cb93a386Sopenharmony_ci 216cb93a386Sopenharmony_ci // compute the hash of the pixel values, like DM does 217cb93a386Sopenharmony_ci img, err := png.Decode(bytes.NewBuffer(pngBytes)) 218cb93a386Sopenharmony_ci if err != nil { 219cb93a386Sopenharmony_ci return "", fmt.Errorf("Not a valid png: %s", err) 220cb93a386Sopenharmony_ci } 221cb93a386Sopenharmony_ci hash := "" 222cb93a386Sopenharmony_ci switch img.(type) { 223cb93a386Sopenharmony_ci case *image.NRGBA: 224cb93a386Sopenharmony_ci i := img.(*image.NRGBA) 225cb93a386Sopenharmony_ci hash = fmt.Sprintf("%x", md5.Sum(i.Pix)) 226cb93a386Sopenharmony_ci case *image.RGBA: 227cb93a386Sopenharmony_ci i := img.(*image.RGBA) 228cb93a386Sopenharmony_ci hash = fmt.Sprintf("%x", md5.Sum(i.Pix)) 229cb93a386Sopenharmony_ci case *image.RGBA64: 230cb93a386Sopenharmony_ci i := img.(*image.RGBA64) 231cb93a386Sopenharmony_ci hash = fmt.Sprintf("%x", md5.Sum(i.Pix)) 232cb93a386Sopenharmony_ci default: 233cb93a386Sopenharmony_ci return "", fmt.Errorf("Unknown type of image") 234cb93a386Sopenharmony_ci } 235cb93a386Sopenharmony_ci 236cb93a386Sopenharmony_ci p := path.Join(*outDir, hash+".png") 237cb93a386Sopenharmony_ci outputFile, err := createOutputFile(p) 238cb93a386Sopenharmony_ci if err != nil { 239cb93a386Sopenharmony_ci return "", fmt.Errorf("Could not create png file %s: %s", p, err) 240cb93a386Sopenharmony_ci } 241cb93a386Sopenharmony_ci if _, err = outputFile.Write(pngBytes); err != nil { 242cb93a386Sopenharmony_ci util.Close(outputFile) 243cb93a386Sopenharmony_ci return "", fmt.Errorf("Could not write to file %s: %s", p, err) 244cb93a386Sopenharmony_ci } 245cb93a386Sopenharmony_ci return hash, outputFile.Close() 246cb93a386Sopenharmony_ci} 247