Skip to content

Commit

Permalink
Merge pull request 'Bunch of fixes' (#10) from ab.hello-again into ma…
Browse files Browse the repository at this point in the history
…ster

Reviewed-on: https://gitea.daedalean.ai/daedalean-github/reqtraq/pulls/10
Reviewed-by: Ian Whittington <[email protected]>
Reviewed-by: Javier Alvarez Garcia <[email protected]>
  • Loading branch information
aleb committed Sep 5, 2023
2 parents 3f20eac + 05d1146 commit 2499084
Show file tree
Hide file tree
Showing 16 changed files with 153 additions and 117 deletions.
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,23 @@ Reqtraq has two main use-cases:
* [go 1.17+](https://golang.org/doc/install)
* [pandoc](https://pandoc.org/installing.html)
* [universal-ctags](https://github.com/universal-ctags/ctags/blob/master/README.md#the-latest-build-and-package) *Note there is also the unmaintained exuberant-ctags which should be avoided.*
* [llvm-14](https://github.com/llvm/llvm-project/releases/tag/llvmorg-14.0.0) *Note this is only needed if reqtraq is built with support for libclang.*
* [clang+llvm-14](https://github.com/llvm/llvm-project/releases/tag/llvmorg-14.0.0), *only needed for building reqtraq with support for libclang.*

### Installation
Basic installation can be done with:
```
$ go install github.com/daedaleanai/reqtraq@latest
$ export PATH=$PATH:$GOPATH/bin
```

If you would like to build reqtraq with support for libclang, you can instead use:
For repos having requirements documents defined in `reqtraq_config.json` with `"codeParser": "clang"` reqtraq needs libclang support. Install the `libclang-14-dev` package on Ubuntu, or download clang+llvm-14 from their release page and unpack it somewhere. Set `LLVM_LIB` accordingly and build reqtraq enabling the "clang" tag:
```
$ export CGO_LDFLAGS="-L${PATH_TO_LLVM_LIB} -Wl,-rpath=${PATH_TO_LLVM_LIB}"
$ LLVM_LIB=/usr/lib/llvm-14/lib
$ LLVM_LIB=~/clang+llvm-14.0.0-x86_64-linux-gnu-ubuntu-18.04/lib
$ export CGO_LDFLAGS="-L${LLVM_LIB} -Wl,-rpath=${LLVM_LIB}"
$ go install --tags clang github.com/daedaleanai/reqtraq@latest
$ export PATH=$PATH:$GOPATH/bin
```
Before running the installation, make sure to download llvm-14 and replace `${PATH_TO_LLVM_LIB}` with
the path to the `lib` folder inside the llvm release where `libclang.so` can be found.

## Using Reqtraq
Reqtraq is tightly integrated with Git. See the certification documents in the `certdocs` directory for some good examples.
Expand Down
31 changes: 29 additions & 2 deletions cmd/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@ package cmd
import (
"fmt"
"log"
"os"
"reflect"
"runtime"
"strings"

"github.com/daedaleanai/cobra"
"github.com/daedaleanai/reqtraq/config"
"github.com/daedaleanai/reqtraq/linepipes"
"github.com/daedaleanai/reqtraq/repos"
"github.com/daedaleanai/reqtraq/util"
"github.com/pkg/errors"
)

var rootCmd = &cobra.Command{
Expand All @@ -33,7 +37,7 @@ func setupConfiguration() error {

cfg, err := config.ParseConfig(baseRepoPath)
if err != nil {
return fmt.Errorf("Error parsing `reqtraq_config.json` file in current repo: %v", err)
return errors.Wrap(err, "Error parsing `reqtraq_config.json` file in current repo")
}

reqtraqConfig = &cfg
Expand Down Expand Up @@ -68,9 +72,32 @@ func init() {
rootCmd.PersistentFlags().BoolVarP(&config.DirectDependenciesOnly, "direct-deps", "d", false, "Only checks the current repository and parents")
}

// Runs the root command and defers the cleanup of the temporary directories until it exits
// Runs the root command and defers the cleanup of the temporary directories
// until it exits.
// @llr REQ-TRAQ-SWL-32, REQ-TRAQ-SWL-59
func RunRootCommand() error {
defer repos.CleanupTemporaryDirectories()
return rootCmd.Execute()
}

// RunAndHandleError returns a RunE function that runs the specified RunE
// function and exits if it returns an error.
// @llr REQ-TRAQ-SWL-59
func RunAndHandleError(runE func(cmd *cobra.Command, args []string) error) func(*cobra.Command, []string) error {
// Wrap the specified runE func in a new func with the same signature.
return func(cmd *cobra.Command, args []string) error {
// At some place in Cobra they lose track of whether the error is
// returned by a RunE function or it's an arguments parsing error.
// That's why we need to handle our errors ourselves and exit with an
// appropriate error code.
// See https://github.com/spf13/cobra/issues/914
if errRun := runE(cmd, args); errRun != nil {
// For example: "github.com/daedaleanai/reqtraq/cmd.runValidate"
s := runtime.FuncForPC(reflect.ValueOf(runE).Pointer()).Name()
s = s[strings.LastIndex(s, "/")+1:]
fmt.Println(errors.Wrap(errRun, s))
os.Exit(1)
}
return nil
}
}
2 changes: 1 addition & 1 deletion cmd/completion_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
)

var completionCmd = &cobra.Command{
Use: "completion [bash|zsh|fish]",
Use: "completion bash|zsh|fish",
Short: "Generate completion script",
Long: `To load completions:
Bash:
Expand Down
4 changes: 2 additions & 2 deletions cmd/list_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ var (
)

var listCmd = &cobra.Command{
Use: "list [CertdocPath]",
Use: "list CERTDOC_PATH",
Short: "Parses and lists the requirements found in certification documents",
Long: `Parses and lists the requirements found in certification documents. Takes a certdoc path as a single argument`,
Args: cobra.ExactValidArgs(1),
ValidArgsFunction: completeCertdocFilename,
RunE: runListCmd,
RunE: RunAndHandleError(runListCmd),
}

// list all requirements in the given certdoc
Expand Down
4 changes: 2 additions & 2 deletions cmd/nextid_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ import (
)

var nextIdCmd = &cobra.Command{
Use: "nextid [CertdocPath]",
Use: "nextid CERTDOC_PATH",
Short: "Generates the next requirement id for the given document",
Long: "Generates the next requirement id for the given document. Takes a certdoc path as a single argument",
Args: cobra.ExactValidArgs(1),
ValidArgsFunction: completeCertdocFilename,
RunE: runNextId,
RunE: RunAndHandleError(runNextId),
}

// runNextId parses a single markdown document for requirements and returns the next available ID
Expand Down
6 changes: 3 additions & 3 deletions cmd/report_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,20 @@ var reportDownCmd = &cobra.Command{
Use: "down",
Short: "Creates an HTML traceability report from system requirements down to code",
Long: "Creates an HTML traceability report from system requirements down to code",
RunE: runReportDownCmd,
RunE: RunAndHandleError(runReportDownCmd),
}
var reportUpCmd = &cobra.Command{
Use: "up",
Short: "Creates an HTML traceability report from code, to LLRs, to HLRs and to system requirements",
Long: "Creates an HTML traceability report from code, to LLRs, to HLRs and to system requirements",
RunE: runReportUpCmd,
RunE: RunAndHandleError(runReportUpCmd),
}

var reportIssuesCmd = &cobra.Command{
Use: "issues",
Short: "Creates an HTML report with all issues found in the requirement documents",
Long: "Creates an HTML report with all issues found in the requirement documents",
RunE: runReportIssuesCmd,
RunE: RunAndHandleError(runReportIssuesCmd),
}

// Registers the report commands
Expand Down
92 changes: 48 additions & 44 deletions cmd/validate_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,26 @@ package cmd

import (
"encoding/json"
"errors"
"fmt"
"log"
"os"

"github.com/daedaleanai/cobra"
"github.com/daedaleanai/reqtraq/config"
"github.com/daedaleanai/reqtraq/diagnostics"
"github.com/daedaleanai/reqtraq/repos"
"github.com/daedaleanai/reqtraq/reqs"
"github.com/pkg/errors"
)

var fValidateStrict *bool
var fValidateJson *string
var fOnlyErrors *bool
var fPrintOnlyErrors *bool

var validateCmd = &cobra.Command{
Use: "validate",
Short: "Validates the requirement documents",
Long: `Runs the validation checks for the requirement documents`,
RunE: runValidate,
RunE: RunAndHandleError(runValidate),
}

type LintMessage struct {
Expand Down Expand Up @@ -52,7 +51,7 @@ func translateSeverityCode(severity diagnostics.IssueSeverity) string {
// Builds a Json file with the issues found after parsing the requirements and code. It only collects
// information for the base repository.
// @llr REQ-TRAQ-SWL-66
func buildJsonIssues(issues []diagnostics.Issue, jsonWriter *json.Encoder) {
func buildJsonIssues(issues []diagnostics.Issue, jsonWriter *json.Encoder) error {
for _, issue := range issues {
// Only report issues for the current repository
if issue.RepoName != repos.BaseRepoName() {
Expand Down Expand Up @@ -108,83 +107,88 @@ func buildJsonIssues(issues []diagnostics.Issue, jsonWriter *json.Encoder) {
log.Fatal("Unhandled IssueType: %r", issue.Type)
}

jsonWriter.Encode(LintMessage{
message := LintMessage{
Name: name,
Code: code,
Severity: translateSeverityCode(issue.Severity),
Path: issue.Path,
Line: issue.Line,
Char: 0,
Description: issue.Error.Error(),
})
}
if err := jsonWriter.Encode(message); err != nil {
return err
}
}
return nil
}

// validate builds the requirement graph, gathering any errors and prints them out. If the strict flag is set return an error.
// createIssuesReport writes the specified requirements issues to a JSON file.
// @llr REQ-TRAQ-SWL-36
func validate(config *config.Config, strict bool) ([]diagnostics.Issue, error) {
rg, err := reqs.BuildGraph(config)
func createIssuesReport(issues []diagnostics.Issue, filePath string) error {
file, err := os.Create(filePath)
if err != nil {
return rg.Issues, err
return err
}
defer file.Close()

hasCriticalErrors := false
for _, issue := range rg.Issues {
if issue.Severity != diagnostics.IssueSeverityNote {
hasCriticalErrors = true
} else if *fOnlyErrors {
continue
}
fmt.Println(issue.Error)
}
jsonWriter := json.NewEncoder(file)
return buildJsonIssues(issues, jsonWriter)
}

if hasCriticalErrors {
if strict {
return rg.Issues, errors.New("ERROR. Validation failed")
// validate prints the issues detected in the requirements graph.
// Returns the count of critical issues and the count of lint messages.
// @llr REQ-TRAQ-SWL-36
func validate(issues []diagnostics.Issue, onlyErrors bool) (int, int) {
criticalErrorsCount := 0
lintErrorsCount := 0
for _, issue := range issues {
if issue.Severity == diagnostics.IssueSeverityNote {
lintErrorsCount += 1
if onlyErrors {
continue
}
} else {
fmt.Println("WARNING. Validation failed")
criticalErrorsCount += 1
}
} else {
fmt.Println("Validation passed")
fmt.Println(issue.Error)
}

return rg.Issues, nil
return criticalErrorsCount, lintErrorsCount
}

// the run command for validate
// @llr REQ-TRAQ-SWL-36
func runValidate(command *cobra.Command, args []string) error {
if err := setupConfiguration(); err != nil {
return err
return errors.Wrap(err, "setup configuration")
}

rg, err := reqs.BuildGraph(reqtraqConfig)
if err != nil {
return errors.Wrap(err, "build graph")
}
issues, err := validate(reqtraqConfig, *fValidateStrict)

if *fValidateJson != "" {
file, fileErr := os.Create(*fValidateJson)
if fileErr != nil {
log.Fatalf("Could not create json file %v\n", fileErr)
if err := createIssuesReport(rg.Issues, *fValidateJson); err != nil {
return errors.Wrap(err, "create report")
}
defer file.Close()

jsonWriter := json.NewEncoder(file)
buildJsonIssues(issues, jsonWriter)
}

if err != nil {
log.Fatal(err)
criticalErrorsCount, _ := validate(rg.Issues, *fPrintOnlyErrors)
if *fValidateStrict && criticalErrorsCount > 0 {
return fmt.Errorf("validation failed: %d critical issues", criticalErrorsCount)
}

// The return error is used when the issued command is not valid, not in the
// case the command actually fails to run. Since no args are used by this command,
// we can always return nil
fmt.Println("Validation passed!")
return nil
}

// Registers the validate command
// @llr REQ-TRAQ-SWL-36
func init() {
fValidateStrict = validateCmd.PersistentFlags().Bool("strict", false, "Exit with error if any validation checks fail")
fValidateJson = validateCmd.PersistentFlags().String("json", "", "Outputs a json file with lint messages in addition to a textual representation of the errors")
fOnlyErrors = validateCmd.PersistentFlags().Bool("only-errors", false, "Only outputs actual errors")
fValidateStrict = validateCmd.PersistentFlags().Bool("strict", false, "Exit with error if any validation issues are found. Only issues with severity 'minor' or 'normal' are counted, linting messages are ignored.")
fValidateJson = validateCmd.PersistentFlags().String("json", "", "Additionally, create a JSON file with all errors and lint messages")
fPrintOnlyErrors = validateCmd.PersistentFlags().Bool("only-errors", false, "Only output actual errors, skipping the lint messages")
rootCmd.AddCommand(validateCmd)
}
9 changes: 2 additions & 7 deletions cmd/validate_cmd_clang_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (

"github.com/daedaleanai/reqtraq/config"
"github.com/daedaleanai/reqtraq/repos"
"github.com/stretchr/testify/assert"
)

// @llr REQ-TRAQ-SWL-36
Expand All @@ -22,12 +21,8 @@ func TestValidateUsingLibClang(t *testing.T) {
t.Fatal(err)
}

actual, err := RunValidate(t, &config)
assert.Empty(t, err, "Got unexpected error")

expected := `Invalid reference in function operator[]@code/include/a.hh:45 in repo ` + "`" + `libclangtest` + "`" + `, REQ-TEST-SWL-12 does not exist.
LLR declarations differ in doThings@code/include/a.hh:134 and doThings@code/a.cc:16.
WARNING. Validation failed`
LLR declarations differ in doThings@code/include/a.hh:134 and doThings@code/a.cc:16.`

checkValidateError(t, actual, expected)
checkValidate(t, &config, expected, "")
}
Loading

0 comments on commit 2499084

Please sign in to comment.