code-intelligence.com/cifuzz@v0.40.0/internal/cmd/coverage/maven/maven.go (about) 1 package maven 2 3 import ( 4 "fmt" 5 "io" 6 "os" 7 "os/signal" 8 "path/filepath" 9 "strings" 10 "syscall" 11 12 "github.com/pkg/errors" 13 14 "code-intelligence.com/cifuzz/internal/build/maven" 15 "code-intelligence.com/cifuzz/internal/cmd/coverage/summary" 16 "code-intelligence.com/cifuzz/internal/cmdutils" 17 "code-intelligence.com/cifuzz/internal/coverage" 18 "code-intelligence.com/cifuzz/pkg/log" 19 "code-intelligence.com/cifuzz/pkg/runfiles" 20 "code-intelligence.com/cifuzz/util/executil" 21 "code-intelligence.com/cifuzz/util/stringutil" 22 ) 23 24 type CoverageGenerator struct { 25 OutputFormat string 26 OutputPath string 27 FuzzTest string 28 ProjectDir string 29 30 Parallel maven.ParallelOptions 31 32 Stderr io.Writer 33 BuildStdout io.Writer 34 BuildStderr io.Writer 35 36 runfilesFinder runfiles.RunfilesFinder 37 } 38 39 func (cov *CoverageGenerator) BuildFuzzTestForCoverage() error { 40 // ensure a finder is set 41 if cov.runfilesFinder == nil { 42 cov.runfilesFinder = runfiles.Finder 43 } 44 45 // Maven tests fail if fuzz tests fail, so we ignore the error here, 46 // so we can still generate the coverage report 47 mavenTestArgs := []string{"-Dmaven.test.failure.ignore=true"} 48 49 // Flags for jazzer 50 mavenTestArgs = append(mavenTestArgs, "-Djazzer.hooks=false") 51 52 // Flags for cifuzz 53 mavenTestArgs = append(mavenTestArgs, 54 "-Pcifuzz", 55 fmt.Sprintf("-Dtest=%s", cov.FuzzTest), 56 "test") 57 58 if cov.Parallel.Enabled { 59 mavenTestArgs = append(mavenTestArgs, "-T") 60 if cov.Parallel.NumJobs != 0 { 61 mavenTestArgs = append(mavenTestArgs, fmt.Sprint(cov.Parallel.NumJobs)) 62 } else { 63 // Use one thread per cpu core 64 mavenTestArgs = append(mavenTestArgs, "1C") 65 } 66 } 67 err := cov.runMavenCommand(mavenTestArgs) 68 if err != nil { 69 return err 70 } 71 72 if cov.OutputPath == "" { 73 // We are using the .cifuzz-build directory 74 // because the build directory is unknown at this point 75 cov.OutputPath = filepath.Join(cov.ProjectDir, ".cifuzz-build", "report") 76 } 77 mavenReportArgs := []string{ 78 "-Pcifuzz", 79 "jacoco:report", 80 fmt.Sprintf("-Dcifuzz.report.output=%s", cov.OutputPath), 81 } 82 83 if cov.OutputFormat == coverage.FormatJacocoXML { 84 mavenReportArgs = append(mavenReportArgs, "-Dcifuzz.report.format=XML") 85 } else { 86 mavenReportArgs = append(mavenReportArgs, "-Dcifuzz.report.format=XML,HTML") 87 } 88 89 return cov.runMavenCommand(mavenReportArgs) 90 } 91 92 func (cov *CoverageGenerator) GenerateCoverageReport() (string, error) { 93 reportPath := filepath.Join(cov.OutputPath, "jacoco.xml") 94 reportFile, err := os.Open(reportPath) 95 if err != nil { 96 return "", errors.WithStack(err) 97 } 98 defer reportFile.Close() 99 summary.ParseJacocoXML(reportFile).PrintTable(cov.Stderr) 100 101 if cov.OutputFormat == coverage.FormatJacocoXML { 102 return filepath.Join(cov.OutputPath, "jacoco.xml"), nil 103 } 104 105 return cov.OutputPath, nil 106 } 107 108 func (cov *CoverageGenerator) runMavenCommand(args []string) error { 109 mavenCmd, err := cov.runfilesFinder.MavenPath() 110 if err != nil { 111 return err 112 } 113 114 cmdArgs := []string{mavenCmd} 115 cmdArgs = append(cmdArgs, args...) 116 117 cmd := executil.Command(cmdArgs[0], cmdArgs[1:]...) 118 cmd.Dir = cov.ProjectDir 119 cmd.Stdout = cov.BuildStdout 120 cmd.Stderr = cov.BuildStderr 121 log.Debugf("Running maven command: %s", strings.Join(stringutil.QuotedStrings(cmd.Args), " ")) 122 123 sigs := make(chan os.Signal, 1) 124 signal.Notify(sigs, os.Interrupt, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT) 125 go func() { 126 <-sigs 127 err = cmd.TerminateProcessGroup() 128 if err != nil { 129 log.Error(err) 130 } 131 }() 132 133 err = cmd.Run() 134 if err != nil { 135 return cmdutils.WrapExecError(errors.WithStack(err), cmd.Cmd) 136 } 137 return nil 138 }