package gtest import ( "context" "errors" "fmt" "io/ioutil" "log" "os" "os/exec" "path" "time" "golang.flu0r1ne.net/planr" ) const GTEST_CMAKE = "CMakeLists.txt" func mkUnit(tc *planr.TestCase) cmakeUnit { cfg := tc.AdapterConfig().(*GtestConfig) return cmakeUnit { tc.Cname, cfg.joinTests(*cfg.Testfile), cfg.srcList(), }; } func chdir(dir string) { if err := os.Chdir(dir); err != nil { log.Fatal(err) } } type GtestAdapter struct {} func (a *GtestAdapter) Config() planr.AdapterConfig { return planr.AdapterConfig { Name: "gtest", ParseConfig: ParseConfig, ParseDefaultConfig: ParseDefaultConfig, } } func (adapter *GtestAdapter) Build(tcs []*planr.TestCase) { buildDir := adapter.Config().Dir() cmakeFile := path.Join(buildDir, GTEST_CMAKE) units := make([]cmakeUnit, 0) for _, tc := range tcs { fmt.Printf("[R] Building %s (%s)\n", tc.Cname, tc.Path) cfg := tc.AdapterConfig().(*GtestConfig) cfg.ensureSatisfied(tc.Path) units = append(units, mkUnit(tc)) } genCmake(cmakeFile, units) chdir(buildDir) planr.RunCmd("cmake", "-S", ".", "-B", ".") } type ResultFromId map[string] Result func (adapter *GtestAdapter) execTests(cnames []string) ResultFromId { buildDir := adapter.Config().Dir() lut := make(ResultFromId, 0) for _, exe := range cnames { fmt.Printf("[R] Evaluating %s\n", exe) 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()) } } for _, r := range decodeResults(f) { 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 (adapter *GtestAdapter) Evaluate(tcs []*planr.TestCase) { buildDir := adapter.Config().Dir() chdir(buildDir) for _, tc := range tcs { 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) } files := exes(tcs) resultById := adapter.execTests(files) for _, tc := range tcs { result, ok := resultById[id(tc)] // compilation failure if !ok { continue } if !result.pass { tc.Result.Status = planr.RUNTIME_FAILURE } tc.Result.TestOutput = result.testOutput } }