package gtest import ( "context" "errors" "fmt" "io/ioutil" "log" "os" "os/exec" "path" "sync" "time" "golang.flu0r1ne.net/planr" ) const GTEST_CMAKE = "CMakeLists.txt" func makeUnit(tc *planr.TestCase, dirs planr.DirConfig) cmakeUnit { cfg := tc.AdapterConfig().(*GtestConfig) testpath := path.Join(dirs.Tests(), *cfg.Testfile) srclist := cfg.srcList(dirs.Src()) return cmakeUnit { tc.Cname, testpath, srclist, }; } func safeWd() string{ wd, err := os.Getwd() if err != nil { log.Fatalf("Could not get GtestBuildDir %s %v\n", wd, err) } return wd } type ResultFromId map[string] Result func (adapter *GtestAdapter) execTests(cnames []string) ResultFromId { buildDir := safeWd() lut := make(ResultFromId, 0) for _, exe := range cnames { exePath := path.Join(buildDir, exe) f, err := ioutil.TempFile(buildDir, "gtest_adapter_*.json") if err != nil { log.Fatal(err) } ctx, cancel := context.WithTimeout(context.Background(), 9999*time.Millisecond) cmd := exec.CommandContext(ctx, exePath, "--gtest_output=json:" + f.Name()) 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) if err != nil { log.Printf("Could not collect results from %s: %v", exe, err) continue } for _, r := range results { r.testOutput = string(out) lut[exe + "." + r.id] = r } } return lut } // An executable may contain more than one test // Gather all executables and deduplicate them func exes(tcs []*planr.TestCase) []string { set := make(map[string] bool, 0) for _, tc := range tcs { // Tests which have encountered a failure // may not have an executable if tc.Result.Status != planr.PASSING { continue } if(!set[tc.Cname]) { set[tc.Cname] = true } } exes := make([]string, 0) for k := range set { exes = append(exes, k) } return exes } func id(tc *planr.TestCase) string { cfg := tc.AdapterConfig().(*GtestConfig) return tc.Cname + "." + *cfg.Suite + "." + *cfg.Name } func compile(wg * sync.WaitGroup, tc *planr.TestCase) { defer wg.Done() cmd := exec.Command("make", tc.Cname) out, err := cmd.CombinedOutput() tc.Result = new(planr.TestResult) // Don't treat command failure as anything but a build failure if err != nil{ var exiterr *exec.ExitError if errors.As(err, &exiterr) && exiterr.ExitCode() == 0 { log.Fatal(err) } tc.Result.Status = planr.COMPILATION_FAILURE } tc.Result.DebugOutput = string(out) } type GtestAdapter struct { dirs planr.DirConfig } func (a *GtestAdapter) Config() planr.AdapterConfig { return planr.AdapterConfig { Name: "gtest", ParseConfig: ParseConfig, ParseDefaultConfig: ParseDefaultConfig, } } func (a *GtestAdapter) Init(dirs planr.DirConfig) { a.dirs = dirs } func (adapter *GtestAdapter) Build(tcs []*planr.TestCase) { buildDir := safeWd() cmakeFile := path.Join(buildDir, GTEST_CMAKE) units := make([]cmakeUnit, 0) for _, tc := range tcs { cfg := tc.AdapterConfig().(*GtestConfig) cfg.ensureSatisfied(tc.Path) units = append(units, makeUnit(tc, adapter.dirs)) } genCmake(cmakeFile, units) planr.RunCmd("cmake", "-S", ".", "-B", ".") } // ./planr eval 0.93s user 0.16s system 100% cpu 1.089 total func (adapter *GtestAdapter) Evaluate(tcs []*planr.TestCase) { var wg sync.WaitGroup for _, tc := range tcs { wg.Add(1) go compile(&wg, tc) } wg.Wait() files := exes(tcs) resultById := adapter.execTests(files) for _, tc := range tcs { result, ok := resultById[id(tc)] // compilation failure if !ok { fmt.Printf("CAN'T FIND %s: status %d\n", tc.Cname, tc.Result.Status) if tc.Result.Status == planr.PASSING { cfg := tc.AdapterConfig().(*GtestConfig) log.Printf( "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, ) tc.Result.Status = planr.COMPILATION_FAILURE tc.Result.DebugOutput += fmt.Sprintf("planr: Did not find testcase %s in any test executable\n", id(tc)) } continue } if !result.pass { tc.Result.Status = planr.RUNTIME_FAILURE } tc.Result.TestOutput = result.testOutput } }