tests/thirdparty: fix and run as github workflow (#198)

As part of fixing failing third-party tests, this PR significantly
rearchitects their specification and execution.

Third-party tests are now specified in a much more flexible format allowing
more customization on a per-package level. In addition, third-party tests are
now used to auto-generate a Github Actions workflow to perform the tests in
parallel. This not only gives faster feedback on PRs, but will also allow us
to more quickly narrow down on which packages are failing. An additional
workflow also confirms that local execution of third-party tests is consistent
with the Github Actions version. This workflow only runs when tests/thirdparty
itself is changed.
This commit is contained in:
Michael McLoughlin
2021-09-15 23:25:51 -07:00
committed by GitHub
parent b935256fa5
commit 8ff8e3b751
9 changed files with 1443 additions and 373 deletions

View File

@@ -3,51 +3,226 @@ package thirdparty
import (
"encoding/json"
"errors"
"fmt"
"io"
"os"
"path"
"path/filepath"
"strings"
)
// Package defines an integration test based on a third-party package using avo.
type Package struct {
ImportPath string `json:"import_path"` // package import path
Version string `json:"version"` // git sha, tag or branch
Generate [][]string `json:"generate"` // generate commands to run
Dir string `json:"dir"` // working directory for generate commands
Test string `json:"test"` // test path relative to repo root (if empty defaults to ./...)
// GithubRepository specifies a repository on github.
type GithubRepository struct {
Owner string `json:"owner"`
Name string `json:"name"`
}
// Name returns the package name.
func (p Package) Name() string {
return filepath.Base(p.ImportPath)
func (r GithubRepository) String() string {
return path.Join(r.Owner, r.Name)
}
// CloneURL returns the git clone URL.
func (p Package) CloneURL() string {
return "https://" + p.ImportPath + ".git"
func (r GithubRepository) CloneURL() string {
return fmt.Sprintf("https://github.com/%s.git", r)
}
// TestPath returns the paths to run "go test" on, relative to the repository root.
func (p Package) TestPath() string {
if p.Test == "" {
return "./..."
// Step represents a set of commands to run as part of the testing plan for a
// third-party package.
type Step struct {
Name string `json:"name"`
WorkingDirectory string `json:"dir"`
Commands []string `json:"commands"`
}
// Validate step parameters.
func (s *Step) Validate() error {
if s.Name == "" {
return errors.New("missing name")
}
return p.Test
if len(s.Commands) == 0 {
return errors.New("missing commands")
}
return nil
}
// Package defines an integration test based on a third-party package using avo.
type Package struct {
// Repository the package belongs to. At the moment, all packages are
// available on github.
Repository GithubRepository `json:"repository"`
// Version as a git sha, tag or branch.
Version string `json:"version"`
// Sub-package within the repository under test. All file path references
// will be relative to this directory. If empty the root of the repository
// is used.
SubPackage string `json:"pkg"`
// Path to the module file for the avo generator package. This is necessary
// so the integration test can insert replace directives to point at the avo
// version under test.
Module string `json:"module"`
// Setup steps. These run prior to the insertion of avo replace directives,
// therefore should be used if it's necessary to initialize new go modules
// within the repository.
Setup []*Step `json:"setup"`
// Steps to run the avo code generator.
Generate []*Step `json:"generate"` // generate commands to run
// Test steps. If empty, defaults to "go test ./...".
Test []*Step `json:"test"`
}
// ID returns an identifier for the package.
func (p *Package) ID() string {
pkgpath := path.Join(p.Repository.String(), p.SubPackage)
return strings.ReplaceAll(pkgpath, "/", "-")
}
// setdefaults fills in missing parameters to help make the input package
// descriptions less verbose.
func (p *Package) setdefaults() {
for _, stage := range []struct {
Steps []*Step
DefaultName string
}{
{p.Setup, "Setup"},
{p.Generate, "Generate"},
{p.Test, "Test"},
} {
if len(stage.Steps) == 1 && stage.Steps[0].Name == "" {
stage.Steps[0].Name = stage.DefaultName
}
}
}
// Validate package definition.
func (p *Package) Validate() error {
if p.Version == "" {
return errors.New("missing version")
}
if p.Module == "" {
return errors.New("missing module")
}
if len(p.Generate) == 0 {
return errors.New("no generate commands")
}
stages := map[string][]*Step{
"setup": p.Setup,
"generate": p.Generate,
"test": p.Test,
}
for name, steps := range stages {
for _, s := range steps {
if err := s.Validate(); err != nil {
return fmt.Errorf("%s step: %w", name, err)
}
}
}
return nil
}
// Context specifies execution environment parameters for a third-party test.
type Context struct {
// Path to the avo version under test.
AvoDirectory string
// Path to the checked out third-party repository.
RepositoryDirectory string
}
// Steps generates the list of steps required to execute the integration test
// for this package. Context specifies execution environment parameters.
func (p *Package) Steps(c *Context) []*Step {
var steps []*Step
// Optional setup.
steps = append(steps, p.Setup...)
// Replace avo dependency.
const invalid = "v0.0.0-00010101000000-000000000000"
moddir := filepath.Dir(p.Module)
modfile := filepath.Base(p.Module)
steps = append(steps, &Step{
Name: "Avo Module Replacement",
WorkingDirectory: moddir,
Commands: []string{
"go mod edit -modfile=" + modfile + " -require=github.com/mmcloughlin/avo@" + invalid,
"go mod edit -modfile=" + modfile + " -replace=github.com/mmcloughlin/avo=" + c.AvoDirectory,
"go mod tidy -modfile=" + modfile,
},
})
// Run generation.
steps = append(steps, p.Generate...)
// Display changes.
steps = append(steps, &Step{
Name: "Diff",
Commands: []string{"git diff"},
})
// Tests.
if len(p.Test) > 0 {
steps = append(steps, p.Test...)
} else {
steps = append(steps, &Step{
Name: "Test",
Commands: []string{
"go test ./...",
},
})
}
// Prepend sub-directory to every step.
if p.SubPackage != "" {
for _, s := range steps {
s.WorkingDirectory = filepath.Join(p.SubPackage, s.WorkingDirectory)
}
}
return steps
}
// Packages is a collection of third-party integration tests.
type Packages []*Package
func (p Packages) setdefaults() {
for _, pkg := range p {
pkg.setdefaults()
}
}
// Validate the package collection.
func (p Packages) Validate() error {
for _, pkg := range p {
if err := pkg.Validate(); err != nil {
return fmt.Errorf("package %s: %w", pkg.ID(), err)
}
}
return nil
}
// LoadPackages loads a list of package configurations from JSON format.
func LoadPackages(r io.Reader) ([]Package, error) {
var pkgs []Package
func LoadPackages(r io.Reader) (Packages, error) {
var pkgs Packages
d := json.NewDecoder(r)
d.DisallowUnknownFields()
if err := d.Decode(&pkgs); err != nil {
return nil, err
}
pkgs.setdefaults()
return pkgs, nil
}
// LoadPackagesFile loads a list of package configurations from a JSON file.
func LoadPackagesFile(filename string) ([]Package, error) {
func LoadPackagesFile(filename string) (Packages, error) {
f, err := os.Open(filename)
if err != nil {
return nil, err

View File

@@ -5,33 +5,85 @@ import (
"testing"
)
func TestPackageName(t *testing.T) {
p := Package{ImportPath: "github.com/username/repo"}
if p.Name() != "repo" {
t.Fail()
func TestValidateErrors(t *testing.T) {
cases := []struct {
Name string
Item interface{ Validate() error }
ErrorSubstring string
}{
{
Name: "step_missing_name",
Item: &Step{},
ErrorSubstring: "missing name",
},
{
Name: "step_missing_commands",
Item: &Step{
Name: "Setup",
},
ErrorSubstring: "missing commands",
},
{
Name: "package_missing_version",
Item: &Package{
Repository: GithubRepository{Owner: "octocat", Name: "hello-world"},
},
ErrorSubstring: "missing version",
},
{
Name: "package_missing_module",
Item: &Package{
Repository: GithubRepository{Owner: "octocat", Name: "hello-world"},
Version: "v1.0.1",
},
ErrorSubstring: "missing module",
},
{
Name: "package_no_generate_commands",
Item: &Package{
Repository: GithubRepository{Owner: "octocat", Name: "hello-world"},
Version: "v1.0.1",
Module: "avo/go.mod",
},
ErrorSubstring: "no generate commands",
},
{
Name: "package_invalid_generate_commands",
Item: &Package{
Repository: GithubRepository{Owner: "octocat", Name: "hello-world"},
Version: "v1.0.1",
Module: "avo/go.mod",
Generate: []*Step{
{},
},
},
ErrorSubstring: "generate step: missing name",
},
{
Name: "packages_invalid_package",
Item: Packages{
{
Repository: GithubRepository{Owner: "octocat", Name: "hello-world"},
},
},
ErrorSubstring: "missing version",
},
}
for _, c := range cases {
c := c // scopelint
t.Run(c.Name, func(t *testing.T) {
err := c.Item.Validate()
if err == nil {
t.Fatal("expected error; got nil")
}
if !strings.Contains(err.Error(), c.ErrorSubstring) {
t.Fatalf("expected error message to contain %q; got %q", c.ErrorSubstring, err)
}
})
}
}
func TestPackageCloneURL(t *testing.T) {
p := Package{ImportPath: "github.com/username/repo"}
if p.CloneURL() != "https://github.com/username/repo.git" {
t.Fail()
}
}
func TestPackagesTestPath(t *testing.T) {
p := Package{}
if p.TestPath() != "./..." {
t.Fail()
}
p.Test = "./sub"
if p.TestPath() != "./sub" {
t.Fail()
}
}
func TestLoadPackages(t *testing.T) {
func TestLoadPackagesBad(t *testing.T) {
r := strings.NewReader(`[{"unknown_field": "value"}]`)
_, err := LoadPackages(r)
if err == nil {
@@ -39,19 +91,6 @@ func TestLoadPackages(t *testing.T) {
}
}
func TestLoadPackagesFile(t *testing.T) {
pkgs, err := LoadPackagesFile("packages.json")
if err != nil {
t.Fatal(err)
}
for _, pkg := range pkgs {
t.Log(pkg.ImportPath)
}
if len(pkgs) == 0 {
t.Fatal("no packages loaded")
}
}
func TestLoadPackagesFileNotExist(t *testing.T) {
pkgs, err := LoadPackagesFile("does_not_exist")
if pkgs != nil {
@@ -61,3 +100,37 @@ func TestLoadPackagesFileNotExist(t *testing.T) {
t.Fatal("expected non-nil error")
}
}
func TestPackagesFileValid(t *testing.T) {
pkgs, err := LoadPackagesFile("packages.json")
if err != nil {
t.Fatal(err)
}
for _, pkg := range pkgs {
t.Logf("read: %s", pkg.ID())
}
if len(pkgs) == 0 {
t.Fatal("no packages loaded")
}
if err := pkgs.Validate(); err != nil {
t.Fatal(err)
}
}
func TestPackagesFileStepsValid(t *testing.T) {
pkgs, err := LoadPackagesFile("packages.json")
if err != nil {
t.Fatal(err)
}
c := &Context{
AvoDirectory: "avo",
RepositoryDirectory: "repo",
}
for _, pkg := range pkgs {
for _, s := range pkg.Steps(c) {
if err := s.Validate(); err != nil {
t.Errorf("package %s: %s", pkg.ID(), err)
}
}
}
}

148
tests/thirdparty/make_workflow.go vendored Normal file
View File

@@ -0,0 +1,148 @@
//go:build ignore
// +build ignore
package main
import (
"flag"
"log"
"os"
"path"
"path/filepath"
"runtime"
"github.com/mmcloughlin/avo/internal/prnt"
"github.com/mmcloughlin/avo/tests/thirdparty"
)
var (
pkgsfilename = flag.String("pkgs", "", "packages configuration")
output = flag.String("output", "", "path to output file (default stdout)")
)
func main() {
if err := mainerr(); err != nil {
log.Fatal(err)
}
}
func mainerr() error {
flag.Parse()
// Read packages.
pkgs, err := thirdparty.LoadPackagesFile(*pkgsfilename)
if err != nil {
return err
}
if err := pkgs.Validate(); err != nil {
return err
}
// Determine output.
w := os.Stdout
if *output != "" {
f, err := os.Create(*output)
if err != nil {
return err
}
defer f.Close()
w = f
}
// Generate workflow file.
b, err := GenerateWorkflow(pkgs)
if err != nil {
return err
}
// Write output.
if _, err := w.Write(b); err != nil {
return err
}
return nil
}
func GenerateWorkflow(pkgs thirdparty.Packages) ([]byte, error) {
g := &prnt.Generator{}
g.SetIndentString(" ")
_, self, _, _ := runtime.Caller(0)
g.Linef("# Code generated by %s. DO NOT EDIT.", filepath.Base(self))
g.NL()
// Header.
g.Linef("name: packages")
g.Linef("permissions:")
g.Linef(" contents: read")
g.Linef("on:")
g.Linef(" push:")
g.Linef(" branches:")
g.Linef(" - master")
g.Linef(" pull_request:")
// One job per package.
g.NL()
g.Linef("jobs:")
g.Indent()
for _, pkg := range pkgs {
g.Linef("%s:", pkg.ID())
g.Indent()
g.Linef("runs-on: ubuntu-latest")
g.Linef("steps:")
g.Indent()
// Install Go.
g.Linef("- name: Install Go")
g.Linef(" uses: actions/setup-go@37335c7bb261b353407cff977110895fa0b4f7d8 # v2.1.3")
g.Linef(" with:")
g.Linef(" go-version: 1.17.x")
// Checkout avo.
avodir := "avo"
g.Linef("- name: Checkout avo")
g.Linef(" uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4")
g.Linef(" with:")
g.Linef(" path: %s", avodir)
g.Linef(" persist-credentials: false")
// Checkout the third-party package.
pkgdir := pkg.Repository.Name
g.Linef("- name: Checkout %s", pkg.Repository)
g.Linef(" uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4")
g.Linef(" with:")
g.Linef(" repository: %s", pkg.Repository)
g.Linef(" ref: %s", pkg.Version)
g.Linef(" path: %s", pkgdir)
g.Linef(" persist-credentials: false")
// Build steps.
c := &thirdparty.Context{
AvoDirectory: path.Join("${{ github.workspace }}", avodir),
RepositoryDirectory: path.Join("${{ github.workspace }}", pkgdir),
}
for _, step := range pkg.Steps(c) {
g.Linef("- name: %s", step.Name)
g.Linef(" working-directory: %s", path.Join(pkgdir, step.WorkingDirectory))
if len(step.Commands) == 1 {
g.Linef(" run: %s", step.Commands[0])
} else {
g.Linef(" run: |")
for _, cmd := range step.Commands {
g.Linef(" %s", cmd)
}
}
}
g.Dedent()
g.Dedent()
}
g.Dedent()
return g.Result()
}

View File

@@ -1,234 +1,371 @@
[
{
"import_path": "github.com/zeebo/xxh3",
"version": "65f423c10688c362d2a2ce6987b665c72ee7bddd",
"dir": "avo",
"repository": {
"owner": "zeebo",
"name": "xxh3"
},
"version": "v1.0.0-rc1",
"module": "avo/go.mod",
"generate": [
[
"go",
"run",
"-mod=mod",
".",
"-avx",
"-out",
"../vector_avx_amd64.s"
],
[
"go",
"run",
".",
"-sse",
"-out",
"../vector_sse_amd64.s"
]
{
"dir": "avo",
"commands": [
"go run . -avx -out ../accum_vector_avx_amd64.s",
"go run . -sse -out ../accum_vector_sse_amd64.s"
]
}
]
},
{
"import_path": "github.com/dgryski/go-sip13",
"version": "25c5027a8c7bfa6dab4b577e53e5c9068f6e2152",
"generate": [
[
"go",
"run",
"-mod=mod",
"_avo/asm.go",
"-out",
"sip13_amd64.s"
]
]
},
{
"import_path": "github.com/orisano/wyhash",
"version": "32a3f7f6ba4797e2d87dab2969cc9dd63d39cce9",
"generate": [
[
"go",
"run",
"-mod=mod",
"avo/gen.go",
"-out",
"blocks_amd64.s",
"-stubs",
"blocks_amd64.go"
]
]
},
{
"import_path": "github.com/dgryski/go-bloomindex",
"version": "0902316dce158c154b958ee5cfc706c62af29a42",
"generate": [
[
"go",
"run",
"asm.go",
"-out",
"query_amd64.s"
]
]
},
{
"import_path": "github.com/dgryski/go-marvin32",
"version": "7d18f4c6ea7c24b29d1c7a670f8ae40b0812f2e3",
"generate": [
[
"go",
"run",
"-mod=mod",
"asm.go",
"-out",
"marvin_amd64.s"
]
]
},
{
"import_path": "github.com/dgryski/go-speck",
"version": "5b36d4c08d8840c352a153bf37281434ad550ec0",
"generate": [
[
"go",
"run",
"asm.go",
"-out",
"speck_amd64.s"
]
]
},
{
"import_path": "github.com/dgryski/go-chaskey",
"version": "ba454392bc5ab6daae103e15147185f8f4a27dcc",
"generate": [
[
"go",
"run",
"asm.go",
"-out",
"core_amd64.s"
]
]
},
{
"import_path": "github.com/mkevac/gopherconrussia2019",
"version": "235b8b0156a20b4e078b88462e669730f99caeb1",
"dir": "simplesimd",
"generate": [
[
"go",
"run",
"asm.go",
"-out",
"simd.s",
"-stubs",
"stub.go"
]
]
},
{
"import_path": "github.com/phoreproject/bls",
"version": "9d5f85bf4a9badf491a1b9b27fb3344b489bd2c4",
"generate": [
[
"go",
"run",
"-mod=mod",
"asm/asm.go",
"-out",
"primitivefuncs_amd64.s"
]
]
},
{
"import_path": "github.com/klauspost/compress",
"version": "2adf487b3e02f95ce7efd6e4953fda0ae7ecd080",
"dir": "s2/_generate",
"generate": [
[
"go",
"run",
"-mod=mod",
"gen.go",
"-out",
"../encodeblock_amd64.s",
"-stubs",
"../encodeblock_amd64.go",
"-pkg",
"s2"
]
"repository": {
"owner": "dgryski",
"name": "go-sip13"
},
"version": "62edffca92457b3a66125c686137cc5f0fe81672",
"module": "_avo/go.mod",
"setup": [
{
"name": "Setup Root Module",
"commands": [
"go mod init github.com/dgryski/go-sip13"
]
},
{
"name": "Setup Generator Module",
"dir": "_avo",
"commands": [
"sed -i.bak '/+build ignore/d' asm.go",
"go mod init github.com/dgryski/go-sip13/_avo"
]
}
],
"test": "./s2"
},
{
"import_path": "github.com/klauspost/reedsolomon",
"version": "922778284547557265cff0f903ab5f4c27e40ae9",
"dir": "_gen",
"generate": [
[
"go",
"run",
"-mod=mod",
"gen.go",
"-out",
"../galois_gen_amd64.s",
"-stubs",
"../galois_gen_amd64.go",
"-pkg",
"reedsolomon"
]
{
"dir": "_avo",
"commands": [
"go mod edit -modfile=go.mod -require=github.com/dgryski/go-sip13@v0.0.0-00010101000000-000000000000",
"go mod edit -modfile=go.mod -replace=github.com/dgryski/go-sip13=../",
"go run . -out ../sip13_amd64.s"
]
}
]
},
{
"import_path": "github.com/minio/md5-simd",
"repository": {
"owner": "phoreproject",
"name": "bls"
},
"version": "a88a5ae26844d7293359422888d7c7f69f43c845",
"module": "asm/go.mod",
"setup": [
{
"dir": "asm",
"commands": [
"sed -i.bak '/+build ignore/d' asm.go",
"go mod init github.com/phoreproject/bls/asm"
]
}
],
"generate": [
{
"dir": "asm",
"commands": [
"go mod edit -modfile=go.mod -require=github.com/phoreproject/bls@v0.0.0-00010101000000-000000000000",
"go mod edit -modfile=go.mod -replace=github.com/phoreproject/bls=../",
"go mod download",
"go run . -out ../primitivefuncs_amd64.s"
]
}
]
},
{
"repository": {
"owner": "minio",
"name": "md5-simd"
},
"version": "30ad8af83f6868c2a30c615f3edf1a9366bf3f89",
"dir": "_gen",
"module": "_gen/go.mod",
"generate": [
[
"go",
"run",
"-mod=mod",
"gen.go",
"-out",
"../md5block_amd64.s",
"-stubs",
"../md5block_amd64.go",
"-pkg",
"md5simd"
]
{
"dir": "_gen",
"commands": [
"go run gen.go -out ../md5block_amd64.s -stubs ../md5block_amd64.go -pkg md5simd"
]
}
]
},
{
"import_path": "github.com/zeebo/blake3",
"repository": {
"owner": "zeebo",
"name": "blake3"
},
"version": "25dba572f0e78ec108f0dd79c9c15288f542d7d9",
"dir": "avo",
"module": "avo/go.mod",
"generate": [
[
"go",
"run",
"-mod=mod",
"./avx2",
"-out",
"../avx2/impl.s"
],
[
"go",
"run",
"./sse41",
"-out",
"../sse41/impl.s"
]
{
"dir": "avo",
"commands": [
"go run ./avx2 -out ../avx2/impl.s",
"go run ./sse41 -out ../sse41/impl.s"
]
}
]
},
{
"import_path": "github.com/lukechampine/us",
"version": "dff56a80f83653cb14eeeb57ba6ba3c3e942c412",
"dir": "merkle/blake2b",
"repository": {
"owner": "klauspost",
"name": "reedsolomon"
},
"version": "922778284547557265cff0f903ab5f4c27e40ae9",
"module": "_gen/go.mod",
"generate": [
[
"go",
"run",
"gen.go",
"-out",
"blake2b_amd64.s"
]
{
"dir": "_gen",
"commands": [
"go run gen.go -out ../galois_gen_amd64.s -stubs ../galois_gen_amd64.go -pkg reedsolomon"
]
}
]
},
{
"repository": {
"owner": "orisano",
"name": "wyhash"
},
"version": "32a3f7f6ba4797e2d87dab2969cc9dd63d39cce9",
"module": "avo/go.mod",
"setup": [
{
"name": "Setup Generator Module",
"dir": "avo",
"commands": [
"go mod init github.com/orisano/wyhash/avo"
]
}
],
"test": "./merkle/..."
"generate": [
{
"dir": "avo",
"commands": [
"go run gen.go -out ../blocks_amd64.s -stubs ../blocks_amd64.go -pkg wyhash"
]
}
]
},
{
"repository": {
"owner": "klauspost",
"name": "compress"
},
"version": "2adf487b3e02f95ce7efd6e4953fda0ae7ecd080",
"pkg": "s2",
"module": "_generate/go.mod",
"generate": [
{
"dir": "_generate",
"commands": [
"go run gen.go -out ../encodeblock_amd64.s -stubs ../encodeblock_amd64.go -pkg s2"
]
}
]
},
{
"repository": {
"owner": "dgryski",
"name": "go-bloomindex"
},
"version": "0902316dce158c154b958ee5cfc706c62af29a42",
"module": "avo/go.mod",
"setup": [
{
"name": "Setup Root Module",
"commands": [
"go mod init github.com/dgryski/go-bloomindex",
"go get github.com/dgryski/go-bits@v0.0.0-20180113010104-bd8a69a71dc2"
]
},
{
"name": "Create Generator Module",
"commands": [
"mkdir avo",
"git mv asm.go avo"
]
},
{
"name": "Setup Generator Module",
"dir": "avo",
"commands": [
"sed -i.bak '/+build ignore/d' asm.go",
"go mod init github.com/dgryski/go-bloomindex/avo"
]
}
],
"generate": [
{
"dir": "avo",
"commands": [
"go run asm.go -out ../query_amd64.s"
]
}
]
},
{
"repository": {
"owner": "dgryski",
"name": "go-marvin32"
},
"version": "7d18f4c6ea7c24b29d1c7a670f8ae40b0812f2e3",
"module": "avo/go.mod",
"setup": [
{
"name": "Setup Root Module",
"commands": [
"go mod init github.com/dgryski/go-marvin32"
]
},
{
"name": "Create Generator Module",
"commands": [
"mkdir avo",
"git mv asm.go avo"
]
},
{
"name": "Setup Generator Module",
"dir": "avo",
"commands": [
"sed -i.bak '/+build ignore/d' asm.go",
"go mod init github.com/dgryski/go-marvin32/avo"
]
}
],
"generate": [
{
"dir": "avo",
"commands": [
"go mod edit -modfile=go.mod -require=github.com/dgryski/go-marvin32@v0.0.0-00010101000000-000000000000",
"go mod edit -modfile=go.mod -replace=github.com/dgryski/go-marvin32=../",
"go run asm.go -out ../marvin_amd64.s"
]
}
]
},
{
"repository": {
"owner": "dgryski",
"name": "go-speck"
},
"version": "5b36d4c08d8840c352a153bf37281434ad550ec0",
"module": "avo/go.mod",
"setup": [
{
"name": "Setup Root Module",
"commands": [
"go mod init github.com/dgryski/go-speck",
"go get github.com/dgryski/go-skipjack@v0.0.0-20150401095227-f3a984a142ab"
]
},
{
"name": "Create Generator Module",
"commands": [
"mkdir avo",
"git mv asm.go avo"
]
},
{
"name": "Setup Generator Module",
"dir": "avo",
"commands": [
"sed -i.bak '/+build ignore/d' asm.go",
"go mod init github.com/dgryski/go-speck/avo"
]
}
],
"generate": [
{
"dir": "avo",
"commands": [
"go mod edit -modfile=go.mod -require=github.com/dgryski/go-speck@v0.0.0-00010101000000-000000000000",
"go mod edit -modfile=go.mod -replace=github.com/dgryski/go-speck=../",
"go run asm.go -out ../speck_amd64.s"
]
}
]
},
{
"repository": {
"owner": "dgryski",
"name": "go-chaskey"
},
"version": "ba454392bc5ab6daae103e15147185f8f4a27dcc",
"module": "avo/go.mod",
"setup": [
{
"name": "Setup Root Module",
"commands": [
"go mod init github.com/dgryski/go-chaskey",
"go get github.com/dchest/siphash@v1.2.2"
]
},
{
"name": "Create Generator Module",
"commands": [
"mkdir avo",
"git mv asm.go avo"
]
},
{
"name": "Setup Generator Module",
"dir": "avo",
"commands": [
"sed -i.bak '/+build ignore/d' asm.go",
"go mod init github.com/dgryski/go-chaskey/avo"
]
}
],
"generate": [
{
"dir": "avo",
"commands": [
"go mod edit -modfile=go.mod -require=github.com/dgryski/go-chaskey@v0.0.0-00010101000000-000000000000",
"go mod edit -modfile=go.mod -replace=github.com/dgryski/go-chaskey=../",
"go run asm.go -out ../core_amd64.s"
]
}
]
},
{
"repository": {
"owner": "lukechampine",
"name": "us"
},
"version": "dff56a80f83653cb14eeeb57ba6ba3c3e942c412",
"pkg": "merkle/blake2b",
"module": "avo/go.mod",
"setup": [
{
"name": "Create Generator Module",
"commands": [
"mkdir avo",
"git mv gen.go avo"
]
},
{
"name": "Setup Generator Module",
"dir": "avo",
"commands": [
"sed -i.bak '/+build ignore/d' gen.go",
"go mod init github.com/lukechampine/us/merkle/blake2b/avo"
]
}
],
"generate": [
{
"dir": "avo",
"commands": [
"go run gen.go -out ../blake2b_amd64.s"
]
}
]
}
]

View File

@@ -2,7 +2,6 @@ package thirdparty
import (
"flag"
"os"
"os/exec"
"path/filepath"
"runtime"
@@ -11,6 +10,8 @@ import (
"github.com/mmcloughlin/avo/internal/test"
)
//go:generate go run make_workflow.go -pkgs packages.json -output ../../.github/workflows/packages.yml
// Custom flags.
var (
pkgsfilename = flag.String("pkgs", "", "packages configuration")
@@ -33,7 +34,7 @@ func TestPackages(t *testing.T) {
for _, pkg := range pkgs {
pkg := pkg // scopelint
t.Run(pkg.Name(), func(t *testing.T) {
t.Run(pkg.ID(), func(t *testing.T) {
dir, clean := test.TempDir(t)
if !*preserve {
defer clean()
@@ -54,57 +55,36 @@ func TestPackages(t *testing.T) {
// PackageTest executes an integration test based on a given third-party package.
type PackageTest struct {
*testing.T
Package
*Package
WorkDir string // working directory for the test
Latest bool // use latest version of the package
repopath string // path the repo is cloned to
cwd string // working directory to execute commands in
}
// Run the test.
func (t *PackageTest) Run() {
t.checkout()
t.modinit()
t.replaceavo()
t.diff()
t.generate()
t.diff()
t.test()
t.steps()
}
// checkout the code at the specified version.
func (t *PackageTest) checkout() {
// Clone repo.
dst := filepath.Join(t.WorkDir, t.Name())
t.git("clone", "--quiet", t.CloneURL(), dst)
test.Exec(t.T, "git", "clone", "--quiet", t.Repository.CloneURL(), dst)
t.repopath = dst
t.cd(t.repopath)
// Checkout specific version.
if t.Latest {
t.Log("using latest version")
return
}
t.git("-C", t.repopath, "checkout", "--quiet", t.Version)
test.Exec(t.T, "git", "-C", t.repopath, "checkout", "--quiet", t.Version)
}
// modinit initializes the repo as a go module if it isn't one already.
func (t *PackageTest) modinit() {
// Check if module path already exists.
gomod := filepath.Join(t.repopath, "go.mod")
if _, err := os.Stat(gomod); err == nil {
t.Logf("already a module")
return
}
// Initialize the module.
t.gotool("mod", "init", t.ImportPath)
}
// replaceavo points all avo dependencies to the local version.
func (t *PackageTest) replaceavo() {
func (t *PackageTest) steps() {
// Determine the path to avo.
_, self, _, ok := runtime.Caller(1)
if !ok {
@@ -112,64 +92,17 @@ func (t *PackageTest) replaceavo() {
}
avodir := filepath.Join(filepath.Dir(self), "..", "..")
// Edit all go.mod files in the repo.
err := filepath.Walk(t.repopath, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
// Run steps.
c := &Context{
AvoDirectory: avodir,
RepositoryDirectory: t.repopath,
}
for _, s := range t.Steps(c) {
for _, command := range s.Commands {
cmd := exec.Command("sh", "-c", command)
cmd.Dir = filepath.Join(t.repopath, s.WorkingDirectory)
test.ExecCommand(t.T, cmd)
}
dir, base := filepath.Split(path)
if base != "go.mod" {
return nil
}
t.cd(dir)
t.gotool("mod", "tidy")
t.gotool("get", "github.com/mmcloughlin/avo")
t.gotool("mod", "edit", "-replace=github.com/mmcloughlin/avo="+avodir)
t.gotool("mod", "download")
return nil
})
if err != nil {
t.Fatal(err)
}
t.cd(t.repopath)
}
// generate runs generate commands.
func (t *PackageTest) generate() {
if len(t.Generate) == 0 {
t.Fatal("no commands specified")
}
for _, args := range t.Generate {
cmd := exec.Command(args[0], args[1:]...)
cmd.Dir = filepath.Join(t.repopath, t.Dir)
test.ExecCommand(t.T, cmd)
}
}
// diff runs git diff on the repository.
func (t *PackageTest) diff() {
t.git("-C", t.repopath, "diff")
}
// test runs go test.
func (t *PackageTest) test() {
t.gotool("test", t.TestPath())
}
// git runs a git command.
func (t *PackageTest) git(arg ...string) {
test.Exec(t.T, "git", arg...)
}
// gotool runs a go command.
func (t *PackageTest) gotool(arg ...string) {
cmd := exec.Command(test.GoTool(), arg...)
cmd.Dir = t.cwd
test.ExecCommand(t.T, cmd)
}
// cd sets the working directory.
func (t *PackageTest) cd(dir string) {
t.cwd = dir
}