package gtest import ( "context" "errors" "fmt" "io/ioutil" "log" "os" "os/exec" "path" "reflect" "sort" "time" "golang.flu0r1ne.net/planr" ) type executable struct { exeNm string testpath string srcs []string includeSrc bool compilerOptions string tcs []planr.TestCase } func dieConflictingExeProperty(exe executable, tc planr.TestCase, property string) { log.Fatalf( "Two test cases (including %s) belonging to the same executable (%s) have one or more conflicting properties\nProperty :%s", tc.Cname, exe.testpath, property, ) } func createExecutables(tcs []planr.TestCase) []executable { exes := make(map[string] executable, 0) for _, tc := range tcs { cfg := tc.AdapterConfig().(*Config) file := cfg.Testfile exe, contained := exes[file] // For set comparison sort.Strings(cfg.Srcs) if !contained { exeTcs := make([]planr.TestCase, 1) exeTcs[0] = tc exe := executable { exeNm: planr.Cname("", file), testpath: file, srcs: cfg.Srcs, includeSrc: *cfg.Include_src, compilerOptions: cfg.Compiler_options, tcs: exeTcs, } exes[file] = exe continue } // We could create two different executables for each source list // But, that would be confusing so we're going to disallow it if !reflect.DeepEqual(exe.srcs, cfg.Srcs) { dieConflictingExeProperty(exe, tc, "srcs") } if exe.compilerOptions != cfg.Compiler_options { dieConflictingExeProperty(exe, tc, "compiler_options") } if exe.includeSrc != *cfg.Include_src { dieConflictingExeProperty(exe, tc, "include_src") } exe.tcs = append(exe.tcs, tc) exes[file] = exe } exesList := make([]executable, 0) for _, exe := range exes { exesList = append(exesList, exe) } return exesList } func (exe executable) compile(builddir string) (succeeded bool, buildFailures []planr.TestResult) { cmd := exec.Command("make", "-C", builddir, exe.exeNm + "/fast") out, err := cmd.CombinedOutput() buildFailures = make([]planr.TestResult, 0) outputLog := string(out) if err != nil{ var exiterr *exec.ExitError if errors.As(err, &exiterr) && exiterr.ExitCode() == 0 { log.Fatalf("Unrecoverable build failure: %v", err) } for i := range exe.tcs { res := planr.TestResult {} res.Tc = exe.tcs[i] res.DebugOutput = outputLog res.Status = planr.COMPILATION_FAILURE buildFailures = append(buildFailures, res) } succeeded = false return } succeeded = true return } const TMPFILENAME = "gtest_adapter_*.json" func runGtest(exe string, tc planr.TestCase, builddir string) planr.TestResult { result := planr.TestResult {} result.Tc = tc exePath := path.Join(builddir, exe) cfg := tc.AdapterConfig().(*Config) f, err := ioutil.TempFile(builddir, TMPFILENAME) if err != nil { log.Fatal(err) } timeout := time.Duration(cfg.Timeout) * time.Millisecond ctx, cancel := context.WithTimeout(context.Background(), timeout) jsonFlag := "--gtest_output=json:" + f.Name() testFlag := "--gtest_filter=" + cfg.Suite + "." + cfg.Name cmd := exec.CommandContext(ctx, exePath, jsonFlag, testFlag) defer cancel() defer os.Remove(f.Name()) out, err := cmd.CombinedOutput() if err != nil { var exiterr *exec.ExitError if !errors.As(err, &exiterr) { log.Printf("%v\n", err) os.Exit(exiterr.ExitCode()) } } results, err := decodeResults(f) result.TestOutput = string(out) if err != nil { result.Status = planr.RUNTIME_FAILURE result.TestOutput += fmt.Sprintf("Could not collect results from %s: %v\n", exe, err) return result } if len(results) < 1 { log.Fatalf( "Could not find testcase %s with name=\"%s\" and suite=\"%s\". Does such a test exist in the test source?", tc.Cname, cfg.Name, cfg.Suite, ) } if len(results) > 1 { log.Fatalf("Unexpected number of results, filter should have produced one result") } decodeResult := results[0] if decodeResult.pass { result.Status = planr.PASSING } else { result.Status = planr.RUNTIME_FAILURE } return result } func (exe executable) execute(builddir string) []planr.TestResult { results := make([]planr.TestResult, len(exe.tcs)) for i := range exe.tcs { results[i] = runGtest(exe.exeNm, exe.tcs[i], builddir) } return results }