371967f791
By using npm ci, we can make sure the versions of the helper libraries (e.g. Karma, Jasmine) we are testing with locally is the same as the versions we are using in the continuous integration system. The copying is needed because our docker recipe forces us to run as not root, and this was causing some issues. As a result, I changed the canvaskit test/perf to not re-use the same file as pathkit does so copying was easier and the dependencies between the two modules is broken. Bug: skia:11077 Change-Id: Ib05890d666d3507d4f724a4ae298484629c7932a Reviewed-on: https://skia-review.googlesource.com/c/skia/+/343503 Reviewed-by: Kevin Lubick <kjlubick@google.com>
246 lines
7.2 KiB
Go
246 lines
7.2 KiB
Go
// Copyright 2018 The Chromium Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
package main
|
|
|
|
// This server runs along side the karma tests and listens for POST requests
|
|
// when any test case reports it has output for Gold. See testReporter.js
|
|
// for the browser side part.
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/md5"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"image"
|
|
"image/png"
|
|
"io/ioutil"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"path"
|
|
"strings"
|
|
"sync"
|
|
|
|
"go.skia.org/infra/golden/go/jsonio"
|
|
"go.skia.org/infra/golden/go/types"
|
|
)
|
|
|
|
// This allows us to use upload_dm_results.py out of the box
|
|
const JSON_FILENAME = "dm.json"
|
|
|
|
var (
|
|
outDir = flag.String("out_dir", "/OUT/", "location to dump the Gold JSON and pngs")
|
|
port = flag.String("port", "8081", "Port to listen on.")
|
|
|
|
botId = flag.String("bot_id", "", "swarming bot id (deprecated/unused)")
|
|
browser = flag.String("browser", "Chrome", "Browser Key")
|
|
buildBucketID = flag.String("buildbucket_build_id", "", "Buildbucket build id key")
|
|
builder = flag.String("builder", "", "Builder, like 'Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-PathKit'")
|
|
compiledLanguage = flag.String("compiled_language", "wasm", "wasm or asm.js")
|
|
config = flag.String("config", "Release", "Configuration (e.g. Debug/Release) key")
|
|
gitHash = flag.String("git_hash", "-", "The git commit hash of the version being tested")
|
|
hostOS = flag.String("host_os", "Debian9", "OS Key")
|
|
issue = flag.String("issue", "", "ChangelistID (if tryjob)")
|
|
patchset = flag.Int("patchset", 0, "patchset (if tryjob)")
|
|
taskId = flag.String("task_id", "", "swarming task id")
|
|
sourceType = flag.String("source_type", "pathkit", "Gold Source type, like pathkit,canvaskit")
|
|
)
|
|
|
|
// Received from the JS side.
|
|
type reportBody struct {
|
|
// e.g. "canvas" or "svg"
|
|
OutputType string `json:"output_type"`
|
|
// a base64 encoded PNG image.
|
|
Data string `json:"data"`
|
|
// a name describing the test. Should be unique enough to allow use of grep.
|
|
TestName string `json:"test_name"`
|
|
}
|
|
|
|
// The keys to be used at the top level for all Results.
|
|
var defaultKeys map[string]string
|
|
|
|
// contains all the results reported in through report_gold_data
|
|
var results []*jsonio.Result
|
|
var resultsMutex sync.Mutex
|
|
|
|
func main() {
|
|
flag.Parse()
|
|
|
|
cpuGPU := "CPU"
|
|
if strings.Index(*builder, "-GPU-") != -1 {
|
|
cpuGPU = "GPU"
|
|
}
|
|
defaultKeys = map[string]string{
|
|
"arch": "WASM",
|
|
"browser": *browser,
|
|
"compiled_language": *compiledLanguage,
|
|
"compiler": "emsdk",
|
|
"configuration": *config,
|
|
"cpu_or_gpu": cpuGPU,
|
|
"cpu_or_gpu_value": "Browser",
|
|
"os": *hostOS,
|
|
"source_type": *sourceType,
|
|
}
|
|
|
|
results = []*jsonio.Result{}
|
|
|
|
http.HandleFunc("/report_gold_data", reporter)
|
|
http.HandleFunc("/dump_json", dumpJSON)
|
|
|
|
fmt.Printf("Waiting for gold ingestion on port %s\n", *port)
|
|
|
|
log.Fatal(http.ListenAndServe(":"+*port, nil))
|
|
}
|
|
|
|
// reporter handles when the client reports a test has Gold output.
|
|
// It writes the corresponding PNG to disk and appends a Result, assuming
|
|
// no errors.
|
|
func reporter(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != "POST" {
|
|
http.Error(w, "Only POST accepted", 400)
|
|
return
|
|
}
|
|
defer r.Body.Close()
|
|
|
|
body, err := ioutil.ReadAll(r.Body)
|
|
if err != nil {
|
|
http.Error(w, "Malformed body", 400)
|
|
return
|
|
}
|
|
|
|
testOutput := reportBody{}
|
|
if err := json.Unmarshal(body, &testOutput); err != nil {
|
|
fmt.Println(err)
|
|
http.Error(w, "Could not unmarshal JSON", 400)
|
|
return
|
|
}
|
|
|
|
hash := ""
|
|
if hash, err = writeBase64EncodedPNG(testOutput.Data); err != nil {
|
|
fmt.Println(err)
|
|
http.Error(w, "Could not write image to disk", 500)
|
|
return
|
|
}
|
|
|
|
if _, err := w.Write([]byte("Accepted")); err != nil {
|
|
fmt.Printf("Could not write response: %s\n", err)
|
|
return
|
|
}
|
|
|
|
resultsMutex.Lock()
|
|
defer resultsMutex.Unlock()
|
|
results = append(results, &jsonio.Result{
|
|
Digest: types.Digest(hash),
|
|
Key: map[string]string{
|
|
"name": testOutput.TestName,
|
|
"config": testOutput.OutputType,
|
|
},
|
|
Options: map[string]string{
|
|
"ext": "png",
|
|
},
|
|
})
|
|
}
|
|
|
|
// createOutputFile creates a file and set permissions correctly.
|
|
func createOutputFile(p string) (*os.File, error) {
|
|
outputFile, err := os.Create(p)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Could not open file %s on disk: %s", p, err)
|
|
}
|
|
// Make this accessible (and deletable) by all users
|
|
if err = outputFile.Chmod(0666); err != nil {
|
|
return nil, fmt.Errorf("Could not change permissions of file %s: %s", p, err)
|
|
}
|
|
return outputFile, nil
|
|
}
|
|
|
|
// dumpJSON writes out a JSON file with all the results, typically at the end of
|
|
// all the tests.
|
|
func dumpJSON(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != "POST" {
|
|
http.Error(w, "Only POST accepted", 400)
|
|
return
|
|
}
|
|
|
|
p := path.Join(*outDir, JSON_FILENAME)
|
|
outputFile, err := createOutputFile(p)
|
|
defer outputFile.Close()
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
http.Error(w, "Could not open json file on disk", 500)
|
|
return
|
|
}
|
|
|
|
dmresults := jsonio.GoldResults{
|
|
Builder: *builder,
|
|
ChangelistID: *issue,
|
|
CodeReviewSystem: "gerrit",
|
|
ContinuousIntegrationSystem: "buildbucket",
|
|
GitHash: *gitHash,
|
|
Key: defaultKeys,
|
|
PatchsetOrder: *patchset,
|
|
Results: results,
|
|
TaskID: *taskId,
|
|
TryJobID: *buildBucketID,
|
|
}
|
|
enc := json.NewEncoder(outputFile)
|
|
enc.SetIndent("", " ") // Make it human readable.
|
|
if err := enc.Encode(&dmresults); err != nil {
|
|
fmt.Println(err)
|
|
http.Error(w, "Could not write json to disk", 500)
|
|
return
|
|
}
|
|
fmt.Println("JSON Written")
|
|
}
|
|
|
|
// writeBase64EncodedPNG writes a PNG to disk and returns the md5 of the
|
|
// decoded PNG bytes and any error. This hash is what will be used as
|
|
// the gold digest and the file name.
|
|
func writeBase64EncodedPNG(data string) (string, error) {
|
|
// data starts with something like data:image/png;base64,[data]
|
|
// https://en.wikipedia.org/wiki/Data_URI_scheme
|
|
start := strings.Index(data, ",")
|
|
b := bytes.NewBufferString(data[start+1:])
|
|
pngReader := base64.NewDecoder(base64.StdEncoding, b)
|
|
|
|
pngBytes, err := ioutil.ReadAll(pngReader)
|
|
if err != nil {
|
|
return "", fmt.Errorf("Could not decode base 64 encoding %s", err)
|
|
}
|
|
|
|
// compute the hash of the pixel values, like DM does
|
|
img, err := png.Decode(bytes.NewBuffer(pngBytes))
|
|
if err != nil {
|
|
return "", fmt.Errorf("Not a valid png: %s", err)
|
|
}
|
|
hash := ""
|
|
switch img.(type) {
|
|
case *image.NRGBA:
|
|
i := img.(*image.NRGBA)
|
|
hash = fmt.Sprintf("%x", md5.Sum(i.Pix))
|
|
case *image.RGBA:
|
|
i := img.(*image.RGBA)
|
|
hash = fmt.Sprintf("%x", md5.Sum(i.Pix))
|
|
case *image.RGBA64:
|
|
i := img.(*image.RGBA64)
|
|
hash = fmt.Sprintf("%x", md5.Sum(i.Pix))
|
|
default:
|
|
return "", fmt.Errorf("Unknown type of image")
|
|
}
|
|
|
|
p := path.Join(*outDir, hash+".png")
|
|
outputFile, err := createOutputFile(p)
|
|
defer outputFile.Close()
|
|
if err != nil {
|
|
return "", fmt.Errorf("Could not create png file %s: %s", p, err)
|
|
}
|
|
if _, err = outputFile.Write(pngBytes); err != nil {
|
|
return "", fmt.Errorf("Could not write to file %s: %s", p, err)
|
|
}
|
|
return hash, nil
|
|
}
|