Skip to content

Commit

Permalink
Merge pull request #25 from tfadeyi/improve-test-coverage
Browse files Browse the repository at this point in the history
Improve test coverage in the internal packages
  • Loading branch information
tfadeyi committed Jun 7, 2023
2 parents c5e1f61 + 897f6b8 commit 89d9cd9
Show file tree
Hide file tree
Showing 18 changed files with 360 additions and 77 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ Global Flags:
The Sloth definitions are added through declarative comments, as shown below.

```go
// @sloth.slo service chatgpt
// @sloth service chatgpt
// @sloth.slo name chat-gpt-availability
// @sloth.slo objective 95.0
// @sloth.sli error_query sum(rate(tenant_failed_login_operations_total{client="chat-gpt"}[{{.window}}])) OR on() vector(0)
Expand Down
2 changes: 1 addition & 1 deletion cmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ i.e:
return err
}

logger.Info("Source code was parsed!")
logger.Info("Source code was parsed")
logger.Info("Printing result specification to stdout.")
return generate.WriteSpecification(service, true, "", opts.Formats...)
},
Expand Down
23 changes: 23 additions & 0 deletions internal/generate/generate_test.go
Original file line number Diff line number Diff line change
@@ -1 +1,24 @@
package generate

import (
"github.com/stretchr/testify/assert"
"testing"
)

func TestIsValidOutputFormat(t *testing.T) {
t.Run("Successfully return true if the input format is json", func(t *testing.T) {
assert.True(t, IsValidOutputFormat("json"))
})
t.Run("Successfully return true if the input format is yaml", func(t *testing.T) {
assert.True(t, IsValidOutputFormat("yaml"))
})
t.Run("Successfully return true if the input format is YAML", func(t *testing.T) {
assert.True(t, IsValidOutputFormat("YAML"))
})
t.Run("Successfully return true if the input format is YAML with whitespace", func(t *testing.T) {
assert.True(t, IsValidOutputFormat(" YAML "))
})
t.Run("Fail to return true if the input format is not supported", func(t *testing.T) {
assert.False(t, IsValidOutputFormat("toml"))
})
}
2 changes: 1 addition & 1 deletion internal/parser/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ New creates a new instance of the parser. See options.Option for more info on th
func (p *Parser) Parse(ctx context.Context) (any, error)
```

Parse parses the data source using the given parser configurations
Parse parses the data source for the target annotations using the given parser configurations and returns a parsed specification.



Expand Down
22 changes: 22 additions & 0 deletions internal/parser/lang/lang_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package lang

import (
"github.com/stretchr/testify/assert"
"testing"
)

func TestIsSupportedLanguage(t *testing.T) {
t.Parallel()

t.Run("Successfully return true if Go is the target language", func(t *testing.T) {
assert.True(t, IsSupportedLanguage(Go))
})

t.Run("Successfully return true if Rust is the target language", func(t *testing.T) {
assert.True(t, IsSupportedLanguage(Rust))
})

t.Run("Fail to return true if the language is different from the supported ones", func(t *testing.T) {
assert.False(t, IsSupportedLanguage("Python"))
})
}
2 changes: 1 addition & 1 deletion internal/parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func New(opts ...options.Option) (*Parser, error) {
return &Parser{defaultOpts}, nil
}

// Parse parses the data source using the given parser configurations
// Parse parses the data source for the target annotations using the given parser configurations and returns a parsed specification.
func (p *Parser) Parse(ctx context.Context) (any, error) {
return p.Opts.TargetSpecification.Parse(ctx)
}
4 changes: 2 additions & 2 deletions internal/parser/specification/sloth/grammar/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ var (
)
```

## func [Eval](<https://github.com/tfadeyi/sloth-simple-comments/blob/main/internal/parser/specification/sloth/grammar/grammar.go#L191>)
## func [Eval](<https://github.com/tfadeyi/sloth-simple-comments/blob/main/internal/parser/specification/sloth/grammar/grammar.go#L192>)

```go
func Eval(source string, options ...participle.ParseOption) (*sloth.Spec, error)
```

Eval evaluates the source input against the grammar and returns an instance of \*sloth.Spec
Eval evaluates the source input against the grammar and returns an instance of \*sloth.spec

## type [Grammar](<https://github.com/tfadeyi/sloth-simple-comments/blob/main/internal/parser/specification/sloth/grammar/grammar.go#L15-L18>)

Expand Down
7 changes: 4 additions & 3 deletions internal/parser/specification/sloth/grammar/grammar.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ func (g Grammar) parse() (*sloth.Spec, error) {
Plugin: nil,
},
}

for _, attr := range g.Stmts {
switch attr.Scope.GetType() {
case ".alerting.ticket":
Expand Down Expand Up @@ -176,7 +177,7 @@ func (g Grammar) parse() (*sloth.Spec, error) {
return spec, nil
}

func eval(filename, source string, options ...participle.ParseOption) (*Grammar, error) {
func createGrammar(filename, source string, options ...participle.ParseOption) (*Grammar, error) {
ast, err := participle.Build[Grammar](
participle.Lexer(lexerDefinition),
)
Expand All @@ -187,9 +188,9 @@ func eval(filename, source string, options ...participle.ParseOption) (*Grammar,
return ast.ParseString(filename, source, options...)
}

// Eval evaluates the source input against the grammar and returns an instance of *sloth.Spec
// Eval evaluates the source input against the grammar and returns an instance of *sloth.spec
func Eval(source string, options ...participle.ParseOption) (*sloth.Spec, error) {
grammar, err := eval("", source, options...)
grammar, err := createGrammar("", source, options...)
if err != nil {
return nil, err
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ import "github.com/tfadeyi/slotalk/internal/parser/specification/sloth/language/
## Index

- [type Options](<#type-options>)
- [func NewOptions() *Options](<#func-newoptions>)


## type [Options](<https://github.com/tfadeyi/sloth-simple-comments/blob/main/internal/parser/specification/sloth/language/golang/parser.go#L30-L35>)
## type [Options](<https://github.com/tfadeyi/sloth-simple-comments/blob/main/internal/parser/specification/sloth/language/golang/parser.go#L29-L34>)

Options contains the configuration options available to the Parser

Expand All @@ -24,6 +25,12 @@ type Options struct {
}
```

### func [NewOptions](<https://github.com/tfadeyi/sloth-simple-comments/blob/main/internal/parser/specification/sloth/language/golang/parser.go#L36>)

```go
func NewOptions() *Options
```



Generated by [gomarkdoc](<https://github.com/princjef/gomarkdoc>)
170 changes: 105 additions & 65 deletions internal/parser/specification/sloth/language/golang/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,11 @@ import (
)

type parser struct {
spec *sloth.Spec
sourceFile string
sourceContent io.ReadCloser
includedDirs []string
applicationPackages map[string]*ast.Package
logger *logging.Logger
spec *sloth.Spec
sourceFile string
sourceContent io.ReadCloser
includedDirs []string
logger *logging.Logger
}

// Options contains the configuration options available to the Parser
Expand All @@ -34,55 +33,51 @@ type Options struct {
InputDirectories []string
}

func NewOptions() *Options {
l := logging.NewStandardLogger()
return &Options{
Logger: &l,
SourceFile: "",
SourceContent: nil,
InputDirectories: nil,
}
}

// NewParser client parser performs all checks at initialization time
func NewParser(opts Options) *parser {
func NewParser(opts *Options) *parser {
// create default options, these will be overridden
if opts == nil {
opts = NewOptions()
}

logger := opts.Logger
dirs := opts.InputDirectories
sourceFile := opts.SourceFile
sourceContent := opts.SourceContent

pkgs := map[string]*ast.Package{}
for _, dir := range dirs {
if _, err := os.Stat(dir); errors.Is(err, os.ErrNotExist) {
// skip if dir doesn't exists
continue
}

foundPkgs, err := getPackages(dir)
if err != nil {
logger.Warn(err)
continue
}

for pkgName, pkg := range foundPkgs {
if _, ok := pkgs[pkgName]; !ok {
pkgs[pkgName] = pkg
}
}
}

return &parser{
spec: &sloth.Spec{
Version: sloth.Version,
Service: "",
Labels: nil,
SLOs: nil,
},
sourceFile: sourceFile,
sourceContent: sourceContent,
includedDirs: dirs,
applicationPackages: pkgs,
logger: logger,
sourceFile: sourceFile,
sourceContent: sourceContent,
includedDirs: dirs,
logger: logger,
}
}

func getPackages(dir string) (map[string]*ast.Package, error) {
// getAllGoPackages fetches all the available golang packages in the target directory and subdirectories
func getAllGoPackages(dir string) (map[string]*ast.Package, error) {
fset := token.NewFileSet()
pkgs, err := goparser.ParseDir(fset, dir, nil, goparser.ParseComments)
if err != nil {
return map[string]*ast.Package{}, err
}

// walk through the directories and parse the not already found go packages
err = filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
if d.IsDir() {
foundPkgs, err := goparser.ParseDir(fset, path, nil, goparser.ParseComments)
Expand All @@ -97,9 +92,19 @@ func getPackages(dir string) (map[string]*ast.Package, error) {
}
return err
})
return pkgs, err
if err != nil {
return nil, err
}

if len(pkgs) == 0 {
return nil, errors.Errorf("no go packages were found in the target directory and subdirectories: %s", dir)
}

return pkgs, nil
}

// getFile returns the ast go file struct given filename or an io.Reader. If an io.Reader is passed it will take precedence
// over the filename
func getFile(name string, file io.ReadCloser) (*ast.File, error) {
fset := token.NewFileSet()
if file != nil {
Expand All @@ -108,41 +113,18 @@ func getFile(name string, file io.ReadCloser) (*ast.File, error) {
return goparser.ParseFile(fset, name, file, goparser.ParseComments)
}

func (p parser) Parse(ctx context.Context) (*sloth.Spec, error) {
// collect all aloe error comments from packages and add them to the spec struct
if p.sourceFile != "" || p.sourceContent != nil {
file, err := getFile(p.sourceFile, p.sourceContent)
if err != nil {
return nil, err
}
if err := p.parseComments(file.Comments...); err != nil {
return nil, err
}
return p.spec, nil
}

if len(p.applicationPackages) > 0 {
for _, pkg := range p.applicationPackages {
for _, file := range pkg.Files {
if err := p.parseComments(file.Comments...); err != nil {
p.logger.Warn(err)
continue
}
}
}
}

return p.spec, nil
// getSpec returns the parser specification struct
func (p parser) getSpec() *sloth.Spec {
return p.spec
}

func (p parser) parseComments(comments ...*ast.CommentGroup) error {
// parseSlothAnnotations parses the source code comments for sloth annotations using the sloth grammar.
// It expects only SLO definition per comment group
func (p parser) parseSlothAnnotations(comments ...*ast.CommentGroup) error {
for _, comment := range comments {
newSpec, err := grammar.Eval(strings.TrimSpace(comment.Text()))
switch {
case errors.Is(err, grammar.ErrParseSource):
continue
case err != nil:
p.logger.Warn(err)
if err != nil {
p.warn(err)
continue
}

Expand All @@ -164,3 +146,61 @@ func (p parser) parseComments(comments ...*ast.CommentGroup) error {
}
return nil
}

// Parse will parse the source code for sloth annotations.
// In case of error during parsing, Parse returns an empty sloth.Spec
func (p parser) Parse(ctx context.Context) (*sloth.Spec, error) {
// collect all sloth annotations from the file and add them to the spec struct
if p.sourceFile != "" || p.sourceContent != nil {
file, err := getFile(p.sourceFile, p.sourceContent)
if err != nil {
// error hard as we can't extract more data for the spec
return nil, err
}
if err := p.parseSlothAnnotations(file.Comments...); err != nil {
return nil, err
}
return p.spec, nil
}

applicationPackages := map[string]*ast.Package{}
for _, dir := range p.includedDirs {
if _, err := os.Stat(dir); errors.Is(err, os.ErrNotExist) {
// skip if dir doesn't exists
p.warn(err)
continue
}

foundPkgs, err := getAllGoPackages(dir)
if err != nil {
p.warn(err)
continue
}

for pkgName, pkg := range foundPkgs {
if _, ok := applicationPackages[pkgName]; !ok {
applicationPackages[pkgName] = pkg
}
}
}

// collect all sloth annotations from packages and add them to the spec struct
if len(applicationPackages) > 0 {
for _, pkg := range applicationPackages {
for _, file := range pkg.Files {
if err := p.parseSlothAnnotations(file.Comments...); err != nil {
p.warn(err)
continue
}
}
}
}

return p.spec, nil
}

func (p parser) warn(err error, keyValues ...interface{}) {
if p.logger != nil {
p.logger.Warn(err, keyValues...)
}
}
Loading

0 comments on commit 89d9cd9

Please sign in to comment.