package release_test import ( "encoding/json" "errors" "os" "path/filepath" "slices" "strings" "testing" "go.yaml.in/yaml/v3" ) type releaseConfig struct { Packages map[string]releasePackage `json:"packages"` } type releasePackage struct { Draft bool `json:"draft"` ForceTag bool `json:"force-tag-creation"` ExtraFiles []extraFile `json:"extra-files"` } type extraFile struct { Type string `json:"type"` Path string `json:"path"` JSONPath string `json:"jsonpath"` } type chart struct { Version string `yaml:"version"` AppVersion string `yaml:"appVersion"` } type wails struct { Info struct { ProductVersion string `json:"productVersion"` } `json:"info"` } type workflowFile struct { On struct { Push struct { Paths []string `yaml:"paths"` } `yaml:"push"` } `yaml:"on"` Concurrency workflowConcurrency `yaml:"concurrency"` Jobs map[string]workflowJob `yaml:"jobs"` } type workflowConcurrency struct { Group string `yaml:"group"` CancelInProgress workflowScalar `yaml:"cancel-in-progress"` } type workflowScalar string func (value *workflowScalar) UnmarshalYAML(node *yaml.Node) error { if node.Kind != yaml.ScalarNode { return errors.New("workflow value is not a scalar") } *value = workflowScalar(node.Value) return nil } type workflowJob struct { If string `yaml:"if"` Uses string `yaml:"uses"` Needs stringList `yaml:"needs"` Outputs map[string]string `yaml:"outputs"` Permissions map[string]string `yaml:"permissions"` Steps []workflowStep `yaml:"steps"` Strategy map[string]any `yaml:"strategy"` TimeoutMinutes any `yaml:"timeout-minutes"` With map[string]any `yaml:"with"` } type workflowStep struct { ID string `yaml:"id"` Name string `yaml:"name"` If string `yaml:"if"` Uses string `yaml:"uses"` Run string `yaml:"run"` Env map[string]string `yaml:"env"` With map[string]any `yaml:"with"` } type stringList []string func (values *stringList) UnmarshalYAML(node *yaml.Node) error { switch node.Kind { case yaml.ScalarNode: *values = []string{node.Value} return nil case yaml.SequenceNode: for _, item := range node.Content { if item.Kind != yaml.ScalarNode { return errors.New("need is not a string") } *values = append(*values, item.Value) } return nil default: return errors.New("needs is neither a string nor a list") } } func readJSON[T any](t *testing.T, name string) T { t.Helper() var value T data, err := os.ReadFile(filepath.Join(repositoryRoot(t), name)) if err != nil { t.Fatal(err) } if err := json.Unmarshal(data, &value); err != nil { t.Fatalf("parse %s: %v", name, err) } return value } func readYAML[T any](t *testing.T, name string) T { t.Helper() var value T data, err := os.ReadFile(filepath.Join(repositoryRoot(t), name)) if err != nil { t.Fatal(err) } if err := yaml.Unmarshal(data, &value); err != nil { t.Fatalf("parse %s: %v", name, err) } return value } func repositoryRoot(t *testing.T) string { t.Helper() root, err := filepath.Abs(filepath.Join("..", "..")) if err != nil { t.Fatal(err) } return root } func requirePackage(t *testing.T, config releaseConfig) releasePackage { t.Helper() pkg, ok := config.Packages["."] if !ok { t.Fatal("release-please config has no root package") } return pkg } func requireJob(t *testing.T, workflow workflowFile, name string) workflowJob { t.Helper() job, ok := workflow.Jobs[name] if !ok { t.Fatalf("release artifact workflow has no %s job", name) } return job } func requireStep(t *testing.T, job workflowJob, id string) workflowStep { t.Helper() for _, step := range job.Steps { if step.ID == id { return step } } t.Fatalf("job has no %s step", id) return workflowStep{} } func contains(values []string, want string) bool { return slices.Contains(values, want) } func containsRun(steps []workflowStep, want string) bool { for _, step := range steps { if strings.Contains(step.Run, want) { return true } } return false } type suiteTiers struct { CommitBrowsers []string `json:"commitBrowsers"` NightlyBrowsers []string `json:"nightlyBrowsers"` CommitBudgetMinutes int `json:"commitBudgetMinutes"` Groups []struct { ID string `json:"id"` Runner string `json:"runner"` Tier string `json:"tier"` ObservedMinutes int `json:"observedMinutes"` } `json:"groups"` } type ciTiers struct { BudgetMinutes int `json:"budgetMinutes"` Workflows map[string]struct { Tier string `json:"tier"` JobMinutes int `json:"jobMinutes"` } `json:"workflows"` }