Skip to content

Commit 89d9cd9

Browse files
authored
Merge pull request #25 from tfadeyi/improve-test-coverage
Improve test coverage in the internal packages
2 parents c5e1f61 + 897f6b8 commit 89d9cd9

File tree

18 files changed

+360
-77
lines changed

18 files changed

+360
-77
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ Global Flags:
184184
The Sloth definitions are added through declarative comments, as shown below.
185185
186186
```go
187-
// @sloth.slo service chatgpt
187+
// @sloth service chatgpt
188188
// @sloth.slo name chat-gpt-availability
189189
// @sloth.slo objective 95.0
190190
// @sloth.sli error_query sum(rate(tenant_failed_login_operations_total{client="chat-gpt"}[{{.window}}])) OR on() vector(0)

cmd/init.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ i.e:
8888
return err
8989
}
9090

91-
logger.Info("Source code was parsed!")
91+
logger.Info("Source code was parsed")
9292
logger.Info("Printing result specification to stdout.")
9393
return generate.WriteSpecification(service, true, "", opts.Formats...)
9494
},

internal/generate/generate_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,24 @@
11
package generate
2+
3+
import (
4+
"github.com/stretchr/testify/assert"
5+
"testing"
6+
)
7+
8+
func TestIsValidOutputFormat(t *testing.T) {
9+
t.Run("Successfully return true if the input format is json", func(t *testing.T) {
10+
assert.True(t, IsValidOutputFormat("json"))
11+
})
12+
t.Run("Successfully return true if the input format is yaml", func(t *testing.T) {
13+
assert.True(t, IsValidOutputFormat("yaml"))
14+
})
15+
t.Run("Successfully return true if the input format is YAML", func(t *testing.T) {
16+
assert.True(t, IsValidOutputFormat("YAML"))
17+
})
18+
t.Run("Successfully return true if the input format is YAML with whitespace", func(t *testing.T) {
19+
assert.True(t, IsValidOutputFormat(" YAML "))
20+
})
21+
t.Run("Fail to return true if the input format is not supported", func(t *testing.T) {
22+
assert.False(t, IsValidOutputFormat("toml"))
23+
})
24+
}

internal/parser/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ New creates a new instance of the parser. See options.Option for more info on th
3939
func (p *Parser) Parse(ctx context.Context) (any, error)
4040
```
4141

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

4444

4545

internal/parser/lang/lang_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package lang
2+
3+
import (
4+
"github.com/stretchr/testify/assert"
5+
"testing"
6+
)
7+
8+
func TestIsSupportedLanguage(t *testing.T) {
9+
t.Parallel()
10+
11+
t.Run("Successfully return true if Go is the target language", func(t *testing.T) {
12+
assert.True(t, IsSupportedLanguage(Go))
13+
})
14+
15+
t.Run("Successfully return true if Rust is the target language", func(t *testing.T) {
16+
assert.True(t, IsSupportedLanguage(Rust))
17+
})
18+
19+
t.Run("Fail to return true if the language is different from the supported ones", func(t *testing.T) {
20+
assert.False(t, IsSupportedLanguage("Python"))
21+
})
22+
}

internal/parser/parser.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ func New(opts ...options.Option) (*Parser, error) {
3333
return &Parser{defaultOpts}, nil
3434
}
3535

36-
// Parse parses the data source using the given parser configurations
36+
// Parse parses the data source for the target annotations using the given parser configurations and returns a parsed specification.
3737
func (p *Parser) Parse(ctx context.Context) (any, error) {
3838
return p.Opts.TargetSpecification.Parse(ctx)
3939
}

internal/parser/specification/sloth/grammar/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,13 @@ var (
2727
)
2828
```
2929

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

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

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

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

internal/parser/specification/sloth/grammar/grammar.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ func (g Grammar) parse() (*sloth.Spec, error) {
101101
Plugin: nil,
102102
},
103103
}
104+
104105
for _, attr := range g.Stmts {
105106
switch attr.Scope.GetType() {
106107
case ".alerting.ticket":
@@ -176,7 +177,7 @@ func (g Grammar) parse() (*sloth.Spec, error) {
176177
return spec, nil
177178
}
178179

179-
func eval(filename, source string, options ...participle.ParseOption) (*Grammar, error) {
180+
func createGrammar(filename, source string, options ...participle.ParseOption) (*Grammar, error) {
180181
ast, err := participle.Build[Grammar](
181182
participle.Lexer(lexerDefinition),
182183
)
@@ -187,9 +188,9 @@ func eval(filename, source string, options ...participle.ParseOption) (*Grammar,
187188
return ast.ParseString(filename, source, options...)
188189
}
189190

190-
// Eval evaluates the source input against the grammar and returns an instance of *sloth.Spec
191+
// Eval evaluates the source input against the grammar and returns an instance of *sloth.spec
191192
func Eval(source string, options ...participle.ParseOption) (*sloth.Spec, error) {
192-
grammar, err := eval("", source, options...)
193+
grammar, err := createGrammar("", source, options...)
193194
if err != nil {
194195
return nil, err
195196
}

internal/parser/specification/sloth/language/golang/README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ import "github.com/tfadeyi/slotalk/internal/parser/specification/sloth/language/
99
## Index
1010

1111
- [type Options](<#type-options>)
12+
- [func NewOptions() *Options](<#func-newoptions>)
1213

1314

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

1617
Options contains the configuration options available to the Parser
1718

@@ -24,6 +25,12 @@ type Options struct {
2425
}
2526
```
2627

28+
### func [NewOptions](<https://github.com/tfadeyi/sloth-simple-comments/blob/main/internal/parser/specification/sloth/language/golang/parser.go#L36>)
29+
30+
```go
31+
func NewOptions() *Options
32+
```
33+
2734

2835

2936
Generated by [gomarkdoc](<https://github.com/princjef/gomarkdoc>)

internal/parser/specification/sloth/language/golang/parser.go

Lines changed: 105 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,11 @@ import (
1818
)
1919

2020
type parser struct {
21-
spec *sloth.Spec
22-
sourceFile string
23-
sourceContent io.ReadCloser
24-
includedDirs []string
25-
applicationPackages map[string]*ast.Package
26-
logger *logging.Logger
21+
spec *sloth.Spec
22+
sourceFile string
23+
sourceContent io.ReadCloser
24+
includedDirs []string
25+
logger *logging.Logger
2726
}
2827

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

36+
func NewOptions() *Options {
37+
l := logging.NewStandardLogger()
38+
return &Options{
39+
Logger: &l,
40+
SourceFile: "",
41+
SourceContent: nil,
42+
InputDirectories: nil,
43+
}
44+
}
45+
3746
// NewParser client parser performs all checks at initialization time
38-
func NewParser(opts Options) *parser {
47+
func NewParser(opts *Options) *parser {
48+
// create default options, these will be overridden
49+
if opts == nil {
50+
opts = NewOptions()
51+
}
52+
3953
logger := opts.Logger
4054
dirs := opts.InputDirectories
4155
sourceFile := opts.SourceFile
4256
sourceContent := opts.SourceContent
4357

44-
pkgs := map[string]*ast.Package{}
45-
for _, dir := range dirs {
46-
if _, err := os.Stat(dir); errors.Is(err, os.ErrNotExist) {
47-
// skip if dir doesn't exists
48-
continue
49-
}
50-
51-
foundPkgs, err := getPackages(dir)
52-
if err != nil {
53-
logger.Warn(err)
54-
continue
55-
}
56-
57-
for pkgName, pkg := range foundPkgs {
58-
if _, ok := pkgs[pkgName]; !ok {
59-
pkgs[pkgName] = pkg
60-
}
61-
}
62-
}
63-
6458
return &parser{
6559
spec: &sloth.Spec{
6660
Version: sloth.Version,
6761
Service: "",
6862
Labels: nil,
6963
SLOs: nil,
7064
},
71-
sourceFile: sourceFile,
72-
sourceContent: sourceContent,
73-
includedDirs: dirs,
74-
applicationPackages: pkgs,
75-
logger: logger,
65+
sourceFile: sourceFile,
66+
sourceContent: sourceContent,
67+
includedDirs: dirs,
68+
logger: logger,
7669
}
7770
}
7871

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

80+
// walk through the directories and parse the not already found go packages
8681
err = filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
8782
if d.IsDir() {
8883
foundPkgs, err := goparser.ParseDir(fset, path, nil, goparser.ParseComments)
@@ -97,9 +92,19 @@ func getPackages(dir string) (map[string]*ast.Package, error) {
9792
}
9893
return err
9994
})
100-
return pkgs, err
95+
if err != nil {
96+
return nil, err
97+
}
98+
99+
if len(pkgs) == 0 {
100+
return nil, errors.Errorf("no go packages were found in the target directory and subdirectories: %s", dir)
101+
}
102+
103+
return pkgs, nil
101104
}
102105

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

111-
func (p parser) Parse(ctx context.Context) (*sloth.Spec, error) {
112-
// collect all aloe error comments from packages and add them to the spec struct
113-
if p.sourceFile != "" || p.sourceContent != nil {
114-
file, err := getFile(p.sourceFile, p.sourceContent)
115-
if err != nil {
116-
return nil, err
117-
}
118-
if err := p.parseComments(file.Comments...); err != nil {
119-
return nil, err
120-
}
121-
return p.spec, nil
122-
}
123-
124-
if len(p.applicationPackages) > 0 {
125-
for _, pkg := range p.applicationPackages {
126-
for _, file := range pkg.Files {
127-
if err := p.parseComments(file.Comments...); err != nil {
128-
p.logger.Warn(err)
129-
continue
130-
}
131-
}
132-
}
133-
}
134-
135-
return p.spec, nil
116+
// getSpec returns the parser specification struct
117+
func (p parser) getSpec() *sloth.Spec {
118+
return p.spec
136119
}
137120

138-
func (p parser) parseComments(comments ...*ast.CommentGroup) error {
121+
// parseSlothAnnotations parses the source code comments for sloth annotations using the sloth grammar.
122+
// It expects only SLO definition per comment group
123+
func (p parser) parseSlothAnnotations(comments ...*ast.CommentGroup) error {
139124
for _, comment := range comments {
140125
newSpec, err := grammar.Eval(strings.TrimSpace(comment.Text()))
141-
switch {
142-
case errors.Is(err, grammar.ErrParseSource):
143-
continue
144-
case err != nil:
145-
p.logger.Warn(err)
126+
if err != nil {
127+
p.warn(err)
146128
continue
147129
}
148130

@@ -164,3 +146,61 @@ func (p parser) parseComments(comments ...*ast.CommentGroup) error {
164146
}
165147
return nil
166148
}
149+
150+
// Parse will parse the source code for sloth annotations.
151+
// In case of error during parsing, Parse returns an empty sloth.Spec
152+
func (p parser) Parse(ctx context.Context) (*sloth.Spec, error) {
153+
// collect all sloth annotations from the file and add them to the spec struct
154+
if p.sourceFile != "" || p.sourceContent != nil {
155+
file, err := getFile(p.sourceFile, p.sourceContent)
156+
if err != nil {
157+
// error hard as we can't extract more data for the spec
158+
return nil, err
159+
}
160+
if err := p.parseSlothAnnotations(file.Comments...); err != nil {
161+
return nil, err
162+
}
163+
return p.spec, nil
164+
}
165+
166+
applicationPackages := map[string]*ast.Package{}
167+
for _, dir := range p.includedDirs {
168+
if _, err := os.Stat(dir); errors.Is(err, os.ErrNotExist) {
169+
// skip if dir doesn't exists
170+
p.warn(err)
171+
continue
172+
}
173+
174+
foundPkgs, err := getAllGoPackages(dir)
175+
if err != nil {
176+
p.warn(err)
177+
continue
178+
}
179+
180+
for pkgName, pkg := range foundPkgs {
181+
if _, ok := applicationPackages[pkgName]; !ok {
182+
applicationPackages[pkgName] = pkg
183+
}
184+
}
185+
}
186+
187+
// collect all sloth annotations from packages and add them to the spec struct
188+
if len(applicationPackages) > 0 {
189+
for _, pkg := range applicationPackages {
190+
for _, file := range pkg.Files {
191+
if err := p.parseSlothAnnotations(file.Comments...); err != nil {
192+
p.warn(err)
193+
continue
194+
}
195+
}
196+
}
197+
}
198+
199+
return p.spec, nil
200+
}
201+
202+
func (p parser) warn(err error, keyValues ...interface{}) {
203+
if p.logger != nil {
204+
p.logger.Warn(err, keyValues...)
205+
}
206+
}

0 commit comments

Comments
 (0)