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  }