Permalink
Browse files

fix #44: improve TestReporter and work with panics

  • Loading branch information...
kamilsk committed May 7, 2018
1 parent 15b6176 commit 182331269873a3436a6d2915a9dc10a445787d28
Showing with 150 additions and 53 deletions.
  1. +1 βˆ’4 http/availability/printer.go
  2. +48 βˆ’0 http/availability/printer_test.go
  3. +20 βˆ’45 http/availability/report.go
  4. +56 βˆ’0 http/availability/report_test.go
  5. +12 βˆ’4 main.go
  6. +13 βˆ’0 main_test.go
@@ -58,10 +58,7 @@ func DecodeOutput(enabled bool) func(*Printer) {
return func(p *Printer) {
if enabled {
p.decoder = func(origin string) string {
decoded, err := url.PathUnescape(origin)
if err != nil {
return origin
}
decoded, _ := url.PathUnescape(origin)
return decoded
}
}
@@ -71,6 +71,7 @@ func TestPrinter(t *testing.T) {
{StatusCode: http.StatusFound,
Location: "http://howilive.ru/en/", Redirect: "https://howilive.ru/en/"},
{StatusCode: http.StatusProcessing, Location: "https://twitter.com/ikamilsk"},
{Internal: true, StatusCode: http.StatusOK, Location: "https://kamil.samigullin.info/"},
},
},
{
@@ -81,6 +82,53 @@ func TestPrinter(t *testing.T) {
{StatusCode: http.StatusFound,
Location: "http://howilive.ru/en/", Redirect: "https://howilive.ru/en/"},
{StatusCode: http.StatusProcessing, Location: "https://twitter.com/ikamilsk"},
{Internal: true, StatusCode: http.StatusOK, Location: "https://kamil.samigullin.info/en/"},
},
},
}}
close(data)
var pipe <-chan availability.Site = data
m.On("Sites").Return(pipe)
return m
},
assert.NoError,
"[200] https://kamil.samigullin.info/",
},
{
"extra configured",
func() *availability.Printer {
return availability.NewPrinter(
availability.ColorizeOutput(true),
availability.DecodeOutput(true),
availability.HideError(true),
availability.HideRedirect(true),
availability.OutputForPrinting(buf),
)
},
func() availability.Reporter {
m := &PrinterMock{}
data := make(chan availability.Site, 1)
data <- availability.Site{Pages: []*availability.Page{
{
&availability.Link{StatusCode: http.StatusOK, Location: "https://kamil.samigullin.info/en/"},
[]availability.Link{
{StatusCode: http.StatusServiceUnavailable, Location: "https://github.com/kamilsk"},
{StatusCode: http.StatusForbidden, Location: "https://www.linkedin.com/in/kamilsk"},
{StatusCode: http.StatusFound,
Location: "http://howilive.ru/en/", Redirect: "https://howilive.ru/en/"},
{StatusCode: http.StatusProcessing, Location: "https://twitter.com/ikamilsk"},
{Internal: true, StatusCode: http.StatusOK, Location: "https://kamil.samigullin.info/"},
},
},
{
Link: &availability.Link{StatusCode: http.StatusOK, Location: "https://kamil.samigullin.info/"},
Links: []availability.Link{
{StatusCode: http.StatusServiceUnavailable, Location: "https://github.com/kamilsk"},
{StatusCode: http.StatusForbidden, Location: "https://www.linkedin.com/in/kamilsk"},
{StatusCode: http.StatusFound,
Location: "http://howilive.ru/en/", Redirect: "https://howilive.ru/en/"},
{StatusCode: http.StatusProcessing, Location: "https://twitter.com/ikamilsk"},
{Internal: true, StatusCode: http.StatusOK, Location: "https://kamil.samigullin.info/en/"},
},
},
}}
@@ -33,7 +33,6 @@ type Report struct {
// For prepares report builder for passed websites' URLs.
func (r *Report) For(rawURLs []string) *Report {
r.sites = make([]*Site, 0, len(rawURLs))
r.ready = make(chan Site, len(rawURLs))
for _, rawURL := range rawURLs {
r.sites = append(r.sites, NewSite(rawURL))
}
@@ -42,31 +41,23 @@ func (r *Report) For(rawURLs []string) *Report {

// Fill starts to fetch sites and prepared them for reading.
func (r *Report) Fill() *Report {
wg := &sync.WaitGroup{}
r.ready = make(chan Site, len(r.sites))
for _, site := range r.sites {
wg.Add(1)
go func(site *Site) {
var copied Site
copied.Name = site.Name
defer wg.Done()
defer func() { r.ready <- copied }()
defer errors.Recover(&copied.Error)
site.Error = site.Fetch(r.crawler)
{
copied = *site
pages := make([]*Page, 0, len(site.Pages))
for _, page := range site.Pages {
page := *page
pages = append(pages, &page)
links := make([]Link, len(page.Links))
copy(links, page.Links)
page.Links = links
}
copied.Pages = pages
site.Error = site.Fetch(r.crawler)
{
copied := *site
pages := make([]*Page, 0, len(site.Pages))
for _, page := range site.Pages {
page := *page
pages = append(pages, &page)
links := make([]Link, len(page.Links))
copy(links, page.Links)
page.Links = links
}
}(site)
copied.Pages = pages
r.ready <- copied
}
}
wg.Wait()
close(r.ready)
return r
}
@@ -157,24 +148,14 @@ func (s *Site) listen(events <-chan event) {
barrier := make(map[*Page]map[*Link]struct{})
s.Pages = make([]*Page, 0, len(pages))
for location, page := range pages {
link, found := links[location]
if !found {
panic(errors.Errorf("panic: not consistent fetch result. link %q not found", location))
}
page.Link = link
page.Link = links[location]
s.Pages = append(s.Pages, page)
barrier[page] = make(map[*Link]struct{})
}
for _, linkAndPage := range linkToPage {
linkLocation, pageLocation := linkAndPage[0], linkAndPage[1]
link, found := links[linkLocation]
if !found {
panic(errors.Errorf("panic: not consistent fetch result. link %q not found", linkLocation))
}
page, found := pages[pageLocation]
if !found {
panic(errors.Errorf("panic: not consistent fetch result. page %q not found", pageLocation))
}
link := links[linkLocation]
page := pages[pageLocation]
if _, exists := barrier[page][link]; !exists {
barrier[page][link] = struct{}{}
{
@@ -211,15 +192,9 @@ func hostOrRawURL(u *url.URL, raw string) string {
}

func hasSameHost(link1, link2 string) bool {
u1, err := url.Parse(link1)
if err != nil {
return false
}
u2, err := url.Parse(link2)
if err != nil {
return false
}
return u1.Host == u2.Host
u1, _ := url.Parse(link1)
u2, _ := url.Parse(link2)
return u1 != nil && u2 != nil && u1.Host == u2.Host
}

type event interface {
@@ -104,3 +104,59 @@ func TestReporter(t *testing.T) {
})
}
}

func TestReporter_handlePanic(t *testing.T) {
tests := []struct {
name string
rawURLs []string
reporter func() *availability.Report
expected string
}{
{
"unexpected event",
[]string{"http://test.dev/"},
func() *availability.Report {
crawler := &CrawlerMock{shift: func(to availability.EventBus) {
type unknown struct{ availability.ProblemEvent }
to <- unknown{availability.ProblemEvent{Message: "bad url", Context: ":bad"}}
close(to)
}}
crawler.On("Visit", "http://test.dev/", mock.Anything).Return(nil)
report := availability.NewReport(availability.CrawlerForSites(crawler))
return report
},
"panic: unexpected event type availability_test.unknown",
},
{
"not consistent fetch result",
[]string{"http://test.dev/"},
func() *availability.Report {
crawler := &CrawlerMock{shift: func(to availability.EventBus) {
to <- availability.ResponseEvent{StatusCode: http.StatusOK, Location: "http://test.dev/"}
to <- availability.WalkEvent{Page: "http://test.dev/without-response/", Href: "http://test.dev/"}
close(to)
}}
crawler.On("Visit", "http://test.dev/", mock.Anything).Return(nil)
report := availability.NewReport(availability.CrawlerForSites(crawler))
return report
},
"runtime error: invalid memory address or nil pointer dereference",
},
}
for _, test := range tests {
tc := test
t.Run(test.name, func(t *testing.T) {
assert.Panics(t, func() {
defer func() {
if r := recover(); r != nil {
err, is := r.(error)
assert.True(t, is)
assert.EqualError(t, err, tc.expected)
panic(r)
}
}()
tc.reporter().For(tc.rawURLs).Fill()
})
})
}
}
16 main.go
@@ -7,6 +7,7 @@ import (
"runtime"

"github.com/kamilsk/check/cmd"
"github.com/kamilsk/check/errors"
"github.com/spf13/cobra"
)

@@ -28,6 +29,16 @@ type application struct {

// Run executes the application logic.
func (app application) Run() {
var err error
defer func() {
errors.Recover(&err)
if err != nil {
// so, when `issue` project will be ready
// I have to integrate it to open GitHub issues
// with stack trace from terminal
app.Shutdown(failed)
}
}()
app.Cmd.AddCommand(&cobra.Command{
Use: "version",
Short: "Show application version",
@@ -38,10 +49,7 @@ func (app application) Run() {
},
Version: version,
})
if err := app.Cmd.Execute(); err != nil {
// so, when `issue` project will be ready
// I have to integrate it to open GitHub issues
// with stack trace from terminal
if err = app.Cmd.Execute(); err != nil {
app.Shutdown(failed)
}
app.Shutdown(success)
@@ -48,6 +48,19 @@ func TestApplication_Run(t *testing.T) {
},
failed,
},
{
"panicked run",
func() interface {
AddCommand(...*cobra.Command)
Execute() error
} {
cmd := &CmdMock{}
cmd.On("AddCommand", mock.Anything)
cmd.On("Execute").Run(func(mock.Arguments) { panic("something unexpected") })
return cmd
},
failed,
},
}
for _, test := range tests {
tc := test

0 comments on commit 1823312

Please sign in to comment.