Skip to content

Commit

Permalink
Add the rest of critical scraping unit tests (#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
krzysztofreczek authored Nov 25, 2020
1 parent 0462e40 commit 48fa782
Show file tree
Hide file tree
Showing 3 changed files with 238 additions and 24 deletions.
14 changes: 12 additions & 2 deletions pkg/internal/test/structures.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,12 @@ func NewRootEmptyHasInfoPtr() *RootEmptyHasInfo {
}

func (r RootEmptyHasInfo) Info() model.Info {
return model.ComponentInfo("root")
return model.ComponentInfo(
"root description",
"root technology",
"root tag 1",
"root tag 2",
)
}

type RootEmptyPtrHasInfo struct{}
Expand All @@ -73,7 +78,12 @@ func NewRootEmptyPtrHasInfoPtr() *RootEmptyPtrHasInfo {
}

func (r *RootEmptyPtrHasInfo) Info() model.Info {
return model.ComponentInfo("root")
return model.ComponentInfo(
"root description",
"root technology",
"root tag 1",
"root tag 2",
)
}

type RootWithPublicPointerToPublicComponent struct {
Expand Down
17 changes: 2 additions & 15 deletions pkg/scraper/scraper.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,6 @@ func (s *Scraper) scrap(
return
}

v = normalize(v)

if !s.isScrappable(v) {
return
}
Expand Down Expand Up @@ -158,6 +156,8 @@ func (s *Scraper) getInfoFromInterface(v reflect.Value) (model.Info, bool) {
var ok bool

if v.CanAddr() {
// it allows accessing new pointer by the interface
v = reflect.NewAt(v.Type(), unsafe.Pointer(v.UnsafeAddr())).Elem()
// v.Addr() instead of v supports both value and pointer receiver
info, ok = v.Addr().Interface().(model.HasInfo)
} else if v.CanInterface() {
Expand Down Expand Up @@ -189,19 +189,6 @@ func (s *Scraper) getInfoFromRules(v reflect.Value) (model.Info, bool) {
return model.Info{}, false
}

func normalize(v reflect.Value) reflect.Value {
if !v.CanAddr() {
return v
}

// supports unexported fields
if !v.CanInterface() {
v = reflect.NewAt(v.Type(), unsafe.Pointer(v.UnsafeAddr())).Elem()
}

return v
}

func componentID(v reflect.Value) string {
id := fmt.Sprintf("%s.%s", valuePackage(v), v.Type().Name())
return internal.Hash(id)
Expand Down
231 changes: 224 additions & 7 deletions pkg/scraper/scraper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,59 @@ const (
testPKG = "github.com/krzysztofreczek/go-structurizr/pkg/internal/test"
)

// todo: components matching rule
// todo: scraped info from interface
// todo: scraped info from matching rule
// todo: package matching
func TestScraper_Scrap_package_matching(t *testing.T) {

func TestScraper_Scrap(t *testing.T) {
var tests = []struct {
name string
structure interface{}
packages []string
expectedNumberOfComponents int
}{
{
name: "structure within given package",
structure: test.NewRootEmptyHasInfo(),
packages: []string{
"github.com/krzysztofreczek/go-structurizr/pkg/internal/test",
},
expectedNumberOfComponents: 1,
},
{
name: "structure out of given package",
structure: test.NewRootEmptyHasInfo(),
packages: []string{
"github.com/krzysztofreczek/go-structurizr/pkg/foo",
},
expectedNumberOfComponents: 0,
},
{
name: "structure within one of given packages",
structure: test.NewRootEmptyHasInfo(),
packages: []string{
"github.com/krzysztofreczek/go-structurizr/pkg/foo",
"github.com/krzysztofreczek/go-structurizr/pkg/internal/test",
},
expectedNumberOfComponents: 1,
},
{
name: "structure within iven package prefix",
structure: test.NewRootEmptyHasInfo(),
packages: []string{
"github.com/krzysztofreczek/go-structurizr/pkg",
},
expectedNumberOfComponents: 1,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := scraper.NewConfiguration(tt.packages...)
s := scraper.NewScraper(c)
result := s.Scrap(tt.structure)
require.Len(t, result.Components, tt.expectedNumberOfComponents)
})
}
}

func TestScraper_Scrap_has_info_interface(t *testing.T) {
c := scraper.NewConfiguration(
testPKG,
)
Expand Down Expand Up @@ -513,13 +560,170 @@ func TestScraper_Scrap(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
s := scraper.NewScraper(c)
result := s.Scrap(tt.structure)
requireEqualComponents(t, tt.expectedComponentIDs, result.Components)
requireEqualComponentIDs(t, tt.expectedComponentIDs, result.Components)
requireEqualRelations(t, tt.expectedRelations, result.Relations)
})
}
}

func requireEqualComponents(
func TestScraper_Scrap_has_info_interface_component_info(t *testing.T) {
c := scraper.NewConfiguration(
testPKG,
)
var tests = []struct {
name string
structure interface{}
expectedComponents map[string]model.Component
}{
{
name: "pointer to empty root that implements HasInfo interface",
structure: test.NewRootEmptyHasInfoPtr(),
expectedComponents: map[string]model.Component{
componentID("RootEmptyHasInfo"): {
ID: componentID("RootEmptyHasInfo"),
Kind: "component",
Name: "test.RootEmptyHasInfo",
Description: "root description",
Technology: "root technology",
Tags: []string{"root tag 1", "root tag 2"},
},
},
},
{
name: "pointer to empty root that pointer implements HasInfo interface",
structure: test.NewRootEmptyPtrHasInfoPtr(),
expectedComponents: map[string]model.Component{
componentID("RootEmptyPtrHasInfo"): {
ID: componentID("RootEmptyPtrHasInfo"),
Kind: "component",
Name: "test.RootEmptyPtrHasInfo",
Description: "root description",
Technology: "root technology",
Tags: []string{"root tag 1", "root tag 2"},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := scraper.NewScraper(c)
result := s.Scrap(tt.structure)
requireEqualComponents(t, tt.expectedComponents, result.Components)
})
}
}

func TestScraper_Scrap_rules(t *testing.T) {
c := scraper.NewConfiguration(
testPKG,
)

ruleDefaultMatchAll, err := scraper.NewRule().
WithApplyFunc(func() model.Info {
return model.ComponentInfo(
"match-all description",
"match-all technology",
"match-all tag 1",
"match-all tag 2",
)
}).
Build()
require.NoError(t, err)

ruleMatchPublicComponent, err := scraper.NewRule().
WithNameRegexp("^PublicComponent$").
WithApplyFunc(func() model.Info {
return model.ComponentInfo(
"match-pc description",
"match-pc technology",
"match-pc tag 1",
"match-pc tag 2",
)
}).
Build()
require.NoError(t, err)

ruleMatchPublicComponentInAnotherPackage, err := scraper.NewRule().
WithPkgRegexps("^github.com/krzysztofreczek/go-structurizr/pkg/foo$").
WithNameRegexp("^PublicComponent$").
WithApplyFunc(func() model.Info {
return model.ComponentInfo()
}).
Build()
require.NoError(t, err)

var tests = []struct {
name string
structure interface{}
rules []scraper.Rule
expectedComponents map[string]model.Component
}{
{
name: "no rules",
structure: test.NewRootEmpty(),
rules: make([]scraper.Rule, 0),
expectedComponents: map[string]model.Component{},
},
{
name: "default match-all rule",
structure: test.NewRootWithPublicPointerToPublicComponent(),
rules: []scraper.Rule{ruleDefaultMatchAll},
expectedComponents: map[string]model.Component{
componentID("RootWithPublicPointerToPublicComponent"): {
ID: componentID("RootWithPublicPointerToPublicComponent"),
Kind: "component",
Name: "test.RootWithPublicPointerToPublicComponent",
Description: "match-all description",
Technology: "match-all technology",
Tags: []string{"match-all tag 1", "match-all tag 2"},
},
componentID("PublicComponent"): {
ID: componentID("PublicComponent"),
Kind: "component",
Name: "test.PublicComponent",
Description: "match-all description",
Technology: "match-all technology",
Tags: []string{"match-all tag 1", "match-all tag 2"},
},
},
},
{
name: "match-public-component rule",
structure: test.NewRootWithPublicPointerToPublicComponent(),
rules: []scraper.Rule{ruleMatchPublicComponent},
expectedComponents: map[string]model.Component{
componentID("PublicComponent"): {
ID: componentID("PublicComponent"),
Kind: "component",
Name: "test.PublicComponent",
Description: "match-pc description",
Technology: "match-pc technology",
Tags: []string{"match-pc tag 1", "match-pc tag 2"},
},
},
},
{
name: "match-public-component-in-another-package rule",
structure: test.NewRootWithPublicPointerToPublicComponent(),
rules: []scraper.Rule{ruleMatchPublicComponentInAnotherPackage},
expectedComponents: map[string]model.Component{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := scraper.NewScraper(c)
for _, r := range tt.rules {
err := s.RegisterRule(r)
require.NoError(t, err)
}

result := s.Scrap(tt.structure)
requireEqualComponents(t, tt.expectedComponents, result.Components)
})
}
}

func requireEqualComponentIDs(
t *testing.T,
expectedComponentIDs map[string]struct{},
actualComponents map[string]model.Component,
Expand All @@ -531,6 +735,19 @@ func requireEqualComponents(
}
}

func requireEqualComponents(
t *testing.T,
expectedComponents map[string]model.Component,
actualComponents map[string]model.Component,
) {
require.Len(t, actualComponents, len(expectedComponents))
for id, expectedComponent := range expectedComponents {
actualComponent, contains := actualComponents[id]
require.True(t, contains, "actual components: %+v", actualComponents)
require.Equal(t, expectedComponent, actualComponent)
}
}

func requireEqualRelations(
t *testing.T,
expectedRelations map[string][]string,
Expand Down

0 comments on commit 48fa782

Please sign in to comment.