mirror of
https://github.com/hanzoai/tasks.git
synced 2026-10-04 02:04:03 +00:00
## What changed? <!-- Describe what has changed in this PR --> Add flaky test detection by running modified test suites multiple times - Create `find_altered_tests.go` to gather modified test suites per category. - Update Makefile: - Modify test targets to accept `TEST_ARGS` where it was omitted - Run modified test suites multiple times using `go test -count=$(N_RUNS)` to detect flaky tests. - Update GitHub Actions workflows: - Update `set-up-single-test` job to collect modified test suites and pass them to test jobs. - Adjust test jobs to run modified test suites multiple times per category. - Optimize test execution by running tests once per category with all modified suites. ## Why? <!-- Tell your future self why have you made these changes --> Pragmatic steps to reduce introduction of new flaky tests ## How did you test it? <!-- How have you verified this change? Tested locally? Added a unit test? Checked in staging env? --> ## Potential risks <!-- Assuming the worst case, what can be broken when deploying this change to production? --> ## Documentation <!-- Have you made sure this change doesn't falsify anything currently stated in `docs/`? If significant new behavior is added, have you described that in `docs/`? --> ## Is hotfix candidate? <!-- Is this PR a hotfix candidate or does it require a notification to be sent to the broader community? (Yes/No) --> --------- Co-authored-by: David Reiss <[email protected]> Co-authored-by: Roey Berman <[email protected]>
190 lines
6.2 KiB
Go
190 lines
6.2 KiB
Go
// The MIT License
|
|
//
|
|
// Copyright (c) 2024 Temporal Technologies Inc. All rights reserved.
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
// in the Software without restriction, including without limitation the rights
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
// THE SOFTWARE.
|
|
|
|
package main
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
// find_altered_tests.go
|
|
//
|
|
// This program identifies altered or added test suite names based on the provided categories.
|
|
// It accepts the following inputs via command-line flags:
|
|
//
|
|
// - Category (-c): The category of tests to find (e.g., unit, integration, functional, functional_ndc).
|
|
// - Source Git Reference (-s): The source Git reference (commit SHA, branch, etc.).
|
|
// - Target Git Reference (-t): The target Git reference (commit SHA, branch, etc.).
|
|
//
|
|
// The program outputs environment variable assignments in the format `key=value`,
|
|
// where each key corresponds to a modified test suite category, and the value is a
|
|
// pipe-separated list of altered test suites.
|
|
//
|
|
// Usage:
|
|
// go run find_altered_tests.go -c <category1> -c <category2> ... -s <sourceRef> -t <targetRef>
|
|
|
|
// CategoryDirs maps test categories to their corresponding directories
|
|
// If you update these, please also update the corresponding dirs in the Makefile
|
|
// FUNCTIONAL_TEST_ROOT
|
|
// FUNCTIONAL_TEST_XDC_ROOT
|
|
// FUNCTIONAL_TEST_NDC_ROOT
|
|
// INTEGRATION_TEST_DIRS
|
|
// UNIT_TEST_DIRS
|
|
var CategoryDirs = map[string][]string{
|
|
"unit": {"./client", "./common", "./internal", "./service", "./temporal", "./tools", "./cmd"},
|
|
"integration": {"./common/persistence/tests", "./tools/tests", "./temporaltest", "./internal/temporalite"},
|
|
"functional": {"./tests"},
|
|
"functional_ndc": {"./tests/ndc"},
|
|
"functional_xdc": {"./tests/xdc"},
|
|
}
|
|
|
|
func main() {
|
|
var categories multiFlag
|
|
var sourceRef string
|
|
var targetRef string
|
|
|
|
flag.Var(&categories, "c", "Category of altered tests to find (can specify multiple)")
|
|
flag.StringVar(&sourceRef, "s", "", "Source Git reference (commit SHA, branch, etc.)")
|
|
flag.StringVar(&targetRef, "t", "", "Target Git reference (commit SHA, branch, etc.)")
|
|
flag.Parse()
|
|
|
|
if len(categories) == 0 || sourceRef == "" || targetRef == "" {
|
|
log.Fatalf("Usage: find_altered_tests -c <category1> -c <category2> ... -s <sourceRef> -t <targetRef>")
|
|
}
|
|
|
|
uniqCategories := make(map[string]struct{})
|
|
for _, category := range categories {
|
|
if _, exists := CategoryDirs[category]; !exists {
|
|
log.Fatalf("Unknown category: %s", category)
|
|
}
|
|
uniqCategories[category] = struct{}{}
|
|
}
|
|
|
|
modifiedFiles, err := getModifiedTestFiles(sourceRef, targetRef)
|
|
if err != nil {
|
|
log.Fatalf("Error getting modified test files: %v", err)
|
|
}
|
|
|
|
for category := range uniqCategories {
|
|
dirs := CategoryDirs[category]
|
|
suites, err := findAlteredTestSuites(modifiedFiles, dirs)
|
|
if err != nil {
|
|
log.Fatalf("Error finding altered test suites for category %s: %v", category, err)
|
|
}
|
|
|
|
// Join suites and output the result directly
|
|
joinedSuites := strings.Join(suites, "|")
|
|
fmt.Printf("modified_%s_test_suites=%s\n", category, joinedSuites)
|
|
}
|
|
}
|
|
|
|
// multiFlag allows multiple instances of a flag
|
|
type multiFlag []string
|
|
|
|
func (m *multiFlag) String() string {
|
|
return strings.Join(*m, ",")
|
|
}
|
|
|
|
func (m *multiFlag) Set(value string) error {
|
|
*m = append(*m, value)
|
|
return nil
|
|
}
|
|
|
|
// getModifiedTestFiles runs 'git diff' to find modified '_test.go' files between two references
|
|
func getModifiedTestFiles(sourceRef, targetRef string) ([]string, error) {
|
|
cmd := exec.Command("git", "diff", "--name-only", sourceRef, targetRef)
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
if exitErr, ok := err.(*exec.ExitError); ok {
|
|
return nil, fmt.Errorf("git diff error: %w", exitErr)
|
|
}
|
|
return nil, fmt.Errorf("error running git diff: %w", err)
|
|
}
|
|
lines := strings.Split(strings.TrimSpace(string(output)), "\n")
|
|
var testFiles []string
|
|
for _, line := range lines {
|
|
if strings.HasSuffix(line, "_test.go") {
|
|
testFiles = append(testFiles, line)
|
|
}
|
|
}
|
|
return testFiles, nil
|
|
}
|
|
|
|
// findAlteredTestSuites filters files by the test directories and extracts test suite names from them
|
|
func findAlteredTestSuites(files []string, testDirs []string) ([]string, error) {
|
|
var testSuites []string
|
|
suiteSet := make(map[string]struct{})
|
|
|
|
// to be detected, _test.go file must contain TestXXXXSuite function
|
|
testSuiteRegex := regexp.MustCompile(`func\s+(Test[a-zA-Z0-9_]*Suite)\s*\(`)
|
|
|
|
for _, file := range files {
|
|
if _, err := os.Stat(file); err != nil {
|
|
continue
|
|
}
|
|
|
|
filePath := filepath.Clean(file)
|
|
|
|
if !isInAnyPath(filePath, testDirs) {
|
|
continue
|
|
}
|
|
|
|
content, err := os.ReadFile(file)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Find test suite names in the file
|
|
matches := testSuiteRegex.FindAllStringSubmatch(string(content), -1)
|
|
for _, match := range matches {
|
|
suiteName := match[1]
|
|
if _, exists := suiteSet[suiteName]; !exists {
|
|
suiteSet[suiteName] = struct{}{}
|
|
testSuites = append(testSuites, suiteName)
|
|
}
|
|
}
|
|
}
|
|
|
|
return testSuites, nil
|
|
}
|
|
|
|
// isInAnyPath checks if the file is within any of the specified paths
|
|
func isInAnyPath(file string, paths []string) bool {
|
|
for _, path := range paths {
|
|
cleanPath := filepath.Clean(path)
|
|
if !strings.HasSuffix(cleanPath, string(os.PathSeparator)) {
|
|
cleanPath += string(os.PathSeparator)
|
|
}
|
|
if strings.HasPrefix(file, cleanPath) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|