blob: cce2ccb861239650e4d9b8a8ad8a567a8b2567c3 [file] [log] [blame]
// Copyright 2017 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
import (
"bytes"
"context"
"flag"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"testing"
bq "infra/cmd/bqexport/testing"
)
var genFlag = flag.Bool("test.generate", false, "Instead of testing, regenerate the golden file.")
// TestBQExport tests the correctness of "bqexport"-generated Go code. It does
// this by loading a testing schema protobuf from the "testing" sub-package,
// generating Go code, comparing it to a golden template
// (test_data/golden.gen.go), and finally compiling a test program using it and
// ensuring that the BigQuery-inferred schema from the struct round-trips back
// to the source TableDef protobuf.
//
// If generation is updated, the golden image can automatically be regenerated
// by passing the "-test.generate" flag to the test. This will NOT run the test,
// but will instead have it regenerate the schema.
//
// Finally, the "test_data" directory is a viable "main" package that can be run
// using "go run" to test the round-trip testing logic against the generated
// golden image.
func TestBQExport(t *testing.T) {
t.Parallel()
c := context.Background()
const (
packageName = "main"
structName = "TestSchema"
testOutDir = "test_data"
)
testOutPath := filepath.Join(testOutDir, "golden.gen.go")
if *genFlag {
if err := Export(c, &bq.TestSchemaTable, packageName, structName, testOutPath); err != nil {
t.Fatalf("failed to regenerate golden path: %s", err)
}
return
}
var gen []byte
err := withTempDir(func(tdir string) error {
outPath := filepath.Join(tdir, "test_output.gen.go")
if err := Export(c, &bq.TestSchemaTable, packageName, structName, outPath); err != nil {
return err
}
// Compare against the golden file.
var err error
gen, err = ioutil.ReadFile(outPath)
if err != nil {
t.Fatalf("Could not read output path: %s", err)
}
if err := copyFile(filepath.Join(testOutDir, "main.go"), filepath.Join(tdir, "main.go")); err != nil {
t.Fatalf("Could not copy 'main.go': %s", err)
}
cmd := exec.CommandContext(c, "go", "run", "main.go", "test_output.gen.go")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = tdir
if err := cmd.Run(); err != nil {
t.Fatalf("Could not verify output: %s", err)
}
return nil
})
if err != nil {
t.Fatalf("failed to generate/validate schema file: %s", err)
}
golden, err := ioutil.ReadFile(testOutPath)
if err != nil {
t.Fatalf("failed to read golden file: %s", err)
}
if bytes.Compare(golden, gen) != 0 {
t.Fatalf("generated file does not match golden file")
}
}
func withTempDir(fn func(string) error) error {
// Need to create the temp directory in the source tree so that "go run"
// can discover and use infra's go.mod.
cwd, err := os.Getwd()
if err != nil {
return err
}
tdir, err := ioutil.TempDir(cwd, ".bqexport_test")
if err != nil {
return err
}
defer func() {
os.RemoveAll(tdir)
}()
return fn(tdir)
}
func copyFile(src, dst string) error {
srcF, err := os.Open(src)
if err != nil {
return err
}
defer srcF.Close()
dstF, err := os.Create(dst)
if err != nil {
return err
}
if _, err := io.Copy(dstF, srcF); err != nil {
dstF.Close()
return err
}
return dstF.Close()
}