Skip to content

Commit

Permalink
feat: add unit tests (#1)
Browse files Browse the repository at this point in the history
* test: add unit test for slowloris

* test: add test for SlowBodyReadRequest

* doc: add example

* chore: setup ci

* test: tune test validation

* test: remove racy validations
  • Loading branch information
bcho committed Aug 18, 2023
1 parent ab7a58e commit e7ae4b3
Show file tree
Hide file tree
Showing 12 changed files with 487 additions and 3 deletions.
11 changes: 11 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates

version: 2
updates:
- package-ecosystem: "gomod" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
25 changes: 25 additions & 0 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: "Unit Test"

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
test:
strategy:
matrix:
go-version: [">=1.19.0", ">=1.20.0", ">=1.21.0"]
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Install Go
uses: actions/setup-go@v3
with:
go-version: ${{ matrix.go-version }}
- name: Test
run: |
go test -v -race "./..."
52 changes: 52 additions & 0 deletions example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package turtle

import (
"context"
"net/http"
"net/url"
"time"
)

func ExampleSlowloris() {
u, err := url.Parse("http://127.0.0.1:8080")
if err != nil {
panic(err)
}

s := Slowloris{
Target: Target{
Url: *u,
},
SendGibberish: true,
GibberishInterval: 5 * time.Millisecond,
UserAgents: []string{
"turtle/0.0.1",
"turtle/0.0.1 - slowloris",
},
}

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

_ = s.Run(ctx)
}

func ExampleSlowBodyReadRequest() {
u, err := url.Parse("http://127.0.0.1:8080")
if err != nil {
panic(err)
}

s := SlowBodyReadRequest{
Target: Target{
Url: *u,
},
Method: http.MethodPost,
BodyReadTimeout: 180 * time.Second,
}

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

_ = s.Run(ctx)
}
55 changes: 55 additions & 0 deletions fixture_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package turtle

import (
"context"
"net/url"
"os"
"os/signal"
"strconv"
"testing"
"time"

"github.com/stretchr/testify/require"
)

func getTestDuration(t testing.TB) time.Duration {
t.Helper()

const envKey = "TURTLE_TEST_DURATION"

if v := os.Getenv(envKey);v != "" {
d, err := time.ParseDuration(v)
require.NoError(t, err, "invalid value for %q", envKey)
return d
}

return 3 * time.Second
}

func getTestConnections(t testing.TB) int {
t.Helper()

const envKey = "TURTLE_TEST_CONNECTIONS"

if v := os.Getenv(envKey);v != "" {
d, err := strconv.ParseInt(v, 10, 32)
require.NoError(t, err, "invalid value for %q", envKey)
return int(d)
}

return 10
}

func getTestTarget(t testing.TB, u url.URL) Target {
t.Helper()

return Target{
Url: u,
Duration: getTestDuration(t),
Connections: getTestConnections(t),
}
}

func getTestContext() (context.Context, context.CancelFunc) {
return signal.NotifyContext(context.Background(), os.Interrupt)
}
10 changes: 9 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
module github.com/b4fun/turtle

go 1.20
go 1.20

require github.com/stretchr/testify v1.8.4

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
10 changes: 10 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
123 changes: 123 additions & 0 deletions internal/testhttp/server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package testhttp

import (
"context"
"net"
"net/http"
"net/url"
"sync"
"testing"

"github.com/stretchr/testify/require"
)

func newLocalListener() (net.Listener, error) {
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
if l, err = net.Listen("tcp6", "[::1]:0"); err != nil {
return nil, err
}
}
return l, nil
}

func CreateTestServer(ctx context.Context, t testing.TB, mu ...func(s *http.Server)) (*url.URL, <-chan struct{}) {
t.Helper()

ln, err := newLocalListener()
require.NoError(t, err)
t.Logf("listening at %q", ln.Addr())

t.Cleanup(func() {
_ = ln.Close()
})

server := &http.Server{
Addr: ln.Addr().String(),
}
for _, m := range mu {
m(server)
}

serverStopped := make(chan struct{})
go func() {
_ = server.Serve(ln)
close(serverStopped)
}()
go func() {
<-ctx.Done()
server.Shutdown(context.Background())
}()

u, err := url.Parse("http://" + ln.Addr().String())
require.NoError(t, err)

return u, serverStopped
}

type ConnStateCounters struct {
mu sync.Mutex // protects below
connTimeline []string // value is connection's remote addr
connStatesTimeline map[string][]http.ConnState // key is connection's remote addr
}

func NewConnStateCounters() *ConnStateCounters {
return &ConnStateCounters{
connStatesTimeline: make(map[string][]http.ConnState),
}
}

func (c *ConnStateCounters) ServerConnState(conn net.Conn, state http.ConnState) {
remoteAddr := conn.RemoteAddr().String()

c.mu.Lock()
defer c.mu.Unlock()

statesTimeline, exists := c.connStatesTimeline[remoteAddr]
if !exists {
c.connTimeline = append(c.connTimeline, remoteAddr)
}
c.connStatesTimeline[remoteAddr] = append(statesTimeline, state)
}

func (c *ConnStateCounters) String() string {
c.mu.Lock()
defer c.mu.Unlock()

var s string
for _, addr := range c.connTimeline {
s += addr + ":"
timeline := c.connStatesTimeline[addr]
if len(timeline) == 0 {
s += " (no states)\n"
continue
}
for idx, state := range timeline {
s += state.String()
if idx < len(timeline) - 1 {
s += " -> "
}
}
s += "\n"
}
return s
}

func (c *ConnStateCounters) GetConns() []string {
c.mu.Lock()
defer c.mu.Unlock()

return c.connTimeline[:]
}

func (c *ConnStateCounters) GetConnStateTimeline(addr string) []http.ConnState {
c.mu.Lock()
defer c.mu.Unlock()

v, exists := c.connStatesTimeline[addr]
if !exists {
return nil
}

return v[:]
}
12 changes: 12 additions & 0 deletions options_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package turtle

import (
"testing"

"github.com/stretchr/testify/assert"
)

func Test_Target_defaults(t *testing.T) {
v := &Target{}
assert.NoError(t, v.defaults())
}
2 changes: 1 addition & 1 deletion slow.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func (s *slowReader) Read(p []byte) (n int, err error) {

// SlowBodyReadRequest provides the configurations for simulating slow request body reading attack.
type SlowBodyReadRequest struct {
Target *Target `embed:""`
Target Target `embed:""`

// Method - the HTTP method to use, one of POST / PUT. Defaults to POST.
Method string `name:"http-method" help:"the HTTP method to use, one of POST / PUT. Defaults to POST."`
Expand Down
Loading

0 comments on commit e7ae4b3

Please sign in to comment.