tests/thirdparty: multiple packages per project (#262)

This commit is contained in:
Michael McLoughlin
2022-04-24 20:20:11 -07:00
committed by GitHub
parent 96c4d85995
commit 84071ae4f9
14 changed files with 1051 additions and 894 deletions

View File

@@ -1,4 +1,4 @@
// Package thirdparty executes integration tests based on third-party packages that use avo.
// Package thirdparty executes integration tests based on third-party projects that use avo.
package thirdparty
import (
@@ -45,6 +45,71 @@ type Metadata struct {
Stars int `json:"stars,omitempty"`
}
// Project defines an integration test based on a third-party project using avo.
type Project struct {
// Repository for the project. At the moment, all projects are available on
// github.
Repository GithubRepository `json:"repository"`
// Repository metadata.
Metadata Metadata `json:"metadata"`
// Default git branch. This is used when testing against the latest version.
DefaultBranch string `json:"default_branch,omitempty"`
// Version as a git sha, tag or branch.
Version string `json:"version"`
// If the project test has a known problem, record it by setting this to a
// non-zero avo issue number. If set, the project will be skipped in
// testing.
KnownIssue int `json:"known_issue,omitempty"`
// Packages within the project to test.
Packages []*Package `json:"packages"`
}
func (p *Project) defaults(set bool) {
for _, pkg := range p.Packages {
pkg.defaults(set)
}
}
// Validate project definition.
func (p *Project) Validate() error {
if p.DefaultBranch == "" {
return errors.New("missing default branch")
}
if p.Version == "" {
return errors.New("missing version")
}
if len(p.Packages) == 0 {
return errors.New("missing packages")
}
for _, pkg := range p.Packages {
if err := pkg.Validate(); err != nil {
return fmt.Errorf("package %s: %w", pkg.Name(), err)
}
}
return nil
}
// ID returns an identifier for the project.
func (p *Project) ID() string {
return strings.ReplaceAll(p.Repository.String(), "/", "-")
}
// Skip reports whether the project test should be skipped. If skipped, a known
// issue will be set.
func (p *Project) Skip() bool {
return p.KnownIssue != 0
}
// Reason returns the reason why the test is skipped.
func (p *Project) Reason() string {
return fmt.Sprintf("https://github.com/mmcloughlin/avo/issues/%d", p.KnownIssue)
}
// Step represents a set of commands to run as part of the testing plan for a
// third-party package.
type Step struct {
@@ -64,24 +129,11 @@ func (s *Step) Validate() error {
return nil
}
// Package defines an integration test based on a third-party package using avo.
// Package defines an integration test for a package within a project.
type Package struct {
// Repository the package belongs to. At the moment, all packages are
// available on github.
Repository GithubRepository `json:"repository"`
// Repository metadata.
Metadata Metadata `json:"metadata"`
// Default git branch. This is used when testing against the latest version.
DefaultBranch string `json:"default_branch,omitempty"`
// 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.
// Sub-package within the project 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,omitempty"`
// Path to the module file for the avo generator package. This is necessary
@@ -95,32 +147,10 @@ type Package struct {
Setup []*Step `json:"setup,omitempty"`
// Steps to run the avo code generator.
Generate []*Step `json:"generate"` // generate commands to run
Generate []*Step `json:"generate"`
// Test steps. If empty, defaults to "go test ./...".
Test []*Step `json:"test,omitempty"`
// If the package test has a known problem, record it by setting this to a
// non-zero avo issue number. If set, the package will be skipped in
// testing.
KnownIssue int `json:"known_issue,omitempty"`
}
// ID returns an identifier for the package.
func (p *Package) ID() string {
pkgpath := path.Join(p.Repository.String(), p.SubPackage)
return strings.ReplaceAll(pkgpath, "/", "-")
}
// Skip reports whether the package test should be skipped. If skipped, a known
// issue will be set.
func (p *Package) Skip() bool {
return p.KnownIssue != 0
}
// Reason returns the reason why the test is skipped.
func (p *Package) Reason() string {
return fmt.Sprintf("https://github.com/mmcloughlin/avo/issues/%d", p.KnownIssue)
}
// defaults sets or removes default field values.
@@ -152,12 +182,6 @@ func applydefault(set bool, s, def string) string {
// Validate package definition.
func (p *Package) Validate() error {
if p.DefaultBranch == "" {
return errors.New("missing default branch")
}
if p.Version == "" {
return errors.New("missing version")
}
if p.Module == "" {
return errors.New("missing module")
}
@@ -181,6 +205,19 @@ func (p *Package) Validate() error {
return nil
}
// Name of the package.
func (p *Package) Name() string {
if p.IsRoot() {
return "root"
}
return p.SubPackage
}
// IsRoot reports whether the package is the root of the containing project.
func (p *Package) IsRoot() bool {
return p.SubPackage == ""
}
// Context specifies execution environment parameters for a third-party test.
type Context struct {
// Path to the avo version under test.
@@ -243,37 +280,72 @@ func (p *Package) Steps(c *Context) []*Step {
return steps
}
// Packages is a collection of third-party integration tests.
type Packages []*Package
// Test case for a given package within a project.
type Test struct {
Project *Project
Package *Package
}
func (p Packages) defaults(set bool) {
for _, pkg := range p {
pkg.defaults(set)
// ID returns an identifier for the test case.
func (t *Test) ID() string {
pkgpath := path.Join(t.Project.Repository.String(), t.Package.SubPackage)
return strings.ReplaceAll(pkgpath, "/", "-")
}
// Projects is a collection of third-party integration tests.
type Projects []*Project
func (p Projects) defaults(set bool) {
for _, prj := range p {
prj.defaults(set)
}
}
// 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)
// Validate the project collection.
func (p Projects) Validate() error {
seen := map[string]bool{}
for _, prj := range p {
// Project is valid.
if err := prj.Validate(); err != nil {
return fmt.Errorf("project %s: %w", prj.ID(), err)
}
// No duplicate entries.
id := prj.ID()
if seen[id] {
return fmt.Errorf("duplicate project %q", id)
}
seen[id] = true
}
return nil
}
// Ranked returns a copy of the packages list ranked in desending order of
// Tests returns all test cases for the projects collection.
func (p Projects) Tests() []*Test {
var ts []*Test
for _, prj := range p {
for _, pkg := range prj.Packages {
ts = append(ts, &Test{
Project: prj,
Package: pkg,
})
}
}
return ts
}
// Ranked returns a copy of the projects list ranked in desending order of
// popularity.
func (p Packages) Ranked() Packages {
ranked := append(Packages(nil), p...)
func (p Projects) Ranked() Projects {
ranked := append(Projects(nil), p...)
sort.SliceStable(ranked, func(i, j int) bool {
return ranked[i].Metadata.Stars > ranked[j].Metadata.Stars
})
return ranked
}
// Top returns the top n most popular packages.
func (p Packages) Top(n int) Packages {
// Top returns the top n most popular projects.
func (p Projects) Top(n int) Projects {
top := p.Ranked()
if len(top) > n {
top = top[:n]
@@ -281,44 +353,44 @@ func (p Packages) Top(n int) Packages {
return top
}
// LoadPackages loads a list of package configurations from JSON format.
func LoadPackages(r io.Reader) (Packages, error) {
var pkgs Packages
// LoadProjects loads a list of project configurations from JSON format.
func LoadProjects(r io.Reader) (Projects, error) {
var prjs Projects
d := json.NewDecoder(r)
d.DisallowUnknownFields()
if err := d.Decode(&pkgs); err != nil {
if err := d.Decode(&prjs); err != nil {
return nil, err
}
pkgs.defaults(true)
return pkgs, nil
prjs.defaults(true)
return prjs, nil
}
// LoadPackagesFile loads a list of package configurations from a JSON file.
func LoadPackagesFile(filename string) (Packages, error) {
// LoadProjectsFile loads a list of project configurations from a JSON file.
func LoadProjectsFile(filename string) (Projects, error) {
f, err := os.Open(filename)
if err != nil {
return nil, err
}
defer f.Close()
return LoadPackages(f)
return LoadProjects(f)
}
// StorePackages writes a list of package configurations in JSON format.
func StorePackages(w io.Writer, pkgs Packages) error {
// StoreProjects writes a list of project configurations in JSON format.
func StoreProjects(w io.Writer, prjs Projects) error {
e := json.NewEncoder(w)
e.SetIndent("", " ")
pkgs.defaults(false)
err := e.Encode(pkgs)
pkgs.defaults(true)
prjs.defaults(false)
err := e.Encode(prjs)
prjs.defaults(true)
return err
}
// StorePackagesFile writes a list of package configurations to a JSON file.
func StorePackagesFile(filename string, pkgs Packages) error {
// StoreProjectsFile writes a list of project configurations to a JSON file.
func StoreProjectsFile(filename string, prjs Projects) error {
f, err := os.Create(filename)
if err != nil {
return err
}
defer f.Close()
return StorePackages(f, pkgs)
return StoreProjects(f, prjs)
}