Skip to content
Merged

Fuzz #65

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,7 @@ generate: mod-tidy
build:
GOVERSION=$$(go version) \
goreleaser build --clean --debug --single-target --snapshot

.PHONY: fuzz
fuzz: mod-tidy generate
go test -fuzz='^Fuzz' -fuzztime=10s -v ./internal/server
1 change: 1 addition & 0 deletions cmd/go-cli-github/main.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// Package main implements the command-line interface of a server.
package main

import (
Expand Down
3 changes: 2 additions & 1 deletion cmd/go-cli-github/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"fmt"
"time"

"github.com/smlx/go-cli-github/internal/server"
)
Expand All @@ -11,6 +12,6 @@ type ServeCmd struct{}

// Run the serve command.
func (*ServeCmd) Run() error {
fmt.Println(server.Serve())
fmt.Println(server.New(time.Now).Serve())
return nil
}
10 changes: 9 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,12 @@ module github.com/smlx/go-cli-github

go 1.21

require github.com/alecthomas/kong v0.8.1
require (
github.com/alecthomas/assert/v2 v2.1.0
github.com/alecthomas/kong v0.8.1
)

require (
github.com/alecthomas/repr v0.1.0 // indirect
github.com/hexops/gotextdiff v1.0.3 // indirect
)
51 changes: 50 additions & 1 deletion internal/server/serve.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,55 @@
// Package server implements an example server.
package server

import (
"fmt"
"time"
)

// Server is an example server.
type Server struct {
// now is a function returning a time that the server will consider the
// current instant.
now func() time.Time
}

// New constructs a new Server, which greets people based on the time returned
// by the given nowFunc. If nowFunc is nil, the Server will default to time.Now.
func New(nowFunc func() time.Time) *Server {
return &Server{
now: nowFunc,
}
}

// Serve is an example function.
func Serve() string {
func (*Server) Serve() string {
return "example serve command"
}

// Greet is an example fuzzable function.
// name is free form, but birthDate must be in YYYY-MM-DD format.
func (s *Server) Greet(name, birthDate string) (string, error) {
b, err := time.ParseInLocation("2006-01-02", birthDate, time.Local)
if err != nil {
return "", fmt.Errorf("couldn't parse birthDate: %v", err)
}
now := s.now()
if now.Before(b) {
return "", fmt.Errorf("time travel detected")
}
// calculate time since birthday this year
d := now.Sub(
time.Date(now.Year(), b.Month(), b.Day(), 0, 0, 0, 0, time.Local))
switch {
case d < 0:
// calculate time since birthday last year
d = now.Sub(
time.Date(now.Year()-1, b.Month(), b.Day(), 0, 0, 0, 0, time.Local))
fallthrough
case d > 0:
return fmt.Sprintf("Hello %s, happy belated birthday for %d days ago.",
name, int(d.Hours()/24)), nil
default:
return fmt.Sprintf("Hello %s, happy birthday!", name), nil
}
}
56 changes: 53 additions & 3 deletions internal/server/serve_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,20 @@ package server_test

import (
"testing"
"time"

"github.com/alecthomas/assert/v2"
"github.com/smlx/go-cli-github/internal/server"
)

func nowTestFunc() time.Time {
t, err := time.ParseInLocation("2006-01-02", "2023-12-12", time.Local)
if err != nil {
panic(err)
}
return t
}

func TestServe(t *testing.T) {
var testCases = map[string]struct {
input string
Expand All @@ -15,10 +25,50 @@ func TestServe(t *testing.T) {
}
for name, tc := range testCases {
t.Run(name, func(tt *testing.T) {
result := server.Serve()
if result != tc.expect {
tt.Fatalf("expected %s, got %s", tc.expect, result)
s := server.New(nowTestFunc)
result := s.Serve()
assert.Equal(tt, tc.expect, result, name)
})
}
}

func TestGreet(t *testing.T) {
var testCases = map[string]struct {
input []string
expect string
expectError bool
}{
"boomer": {
input: []string{"Jim", "1963-02-03"},
expect: "Hello Jim, happy belated birthday for 312 days ago.",
},
"the doctor": {
input: []string{"Who", "2963-02-03"},
expect: "time travel detected",
expectError: true,
},
}
for name, tc := range testCases {
t.Run(name, func(tt *testing.T) {
s := server.New(nowTestFunc)
result, err := s.Greet(tc.input[0], tc.input[1])
if tc.expectError {
assert.EqualError(tt, err, tc.expect, name)
} else {
assert.NoError(tt, err, name)
assert.Equal(tt, tc.expect, result, name)
}
})
}
}

func FuzzGreet(f *testing.F) {
f.Add("Joe", "2020-04-02")
f.Fuzz(func(t *testing.T, name, birthDate string) {
s := server.New(nowTestFunc)
out, err := s.Greet(name, birthDate)
if err != nil && out != "" {
t.Errorf("%q, %v", out, err)
}
})
}