Skip to content
This repository has been archived by the owner on Feb 17, 2024. It is now read-only.

timecraft: add integration test suite #37

Merged
merged 2 commits into from
May 31, 2023
Merged
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: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ timecraft.src.go = \
$(wildcard */*/*.go) \
$(wildcard */*/*/*.go)

timecraft: go.mod $(timecraft.src.go)
timecraft: go.mod flatbuffers $(timecraft.src.go)
$(GO) build -o timecraft

clean:
Expand All @@ -32,7 +32,7 @@ generate: flatbuffers
flatbuffers: go.mod $(format.src.go)
$(GO) build ./format/...

test: flatbuffers testdata
test: timecraft testdata
$(GO) test ./...

testdata: $(testdata.go.wasm)
Expand Down
4 changes: 2 additions & 2 deletions internal/cmd/config.go → config.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package cmd
package main

import (
"bytes"
Expand All @@ -24,7 +24,7 @@ Options:
-c, --config Path to the timecraft configuration file (overrides TIMECRAFTCONFIG)
--edit Open $EDITOR to edit the configuration
-h, --help Show usage information
-o, --ouptut format Output format, one of: text, json, yaml
-o, --output format Output format, one of: text, json, yaml
`

var (
Expand Down
4 changes: 2 additions & 2 deletions internal/cmd/describe.go → describe.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package cmd
package main

import (
"context"
Expand Down Expand Up @@ -49,7 +49,7 @@ Examples:
Options:
-c, --config Path to the timecraft configuration file (overrides TIMECRAFTCONFIG)
-h, --help Show this usage information
-o, --ouptut format Output format, one of: text, json, yaml
-o, --output format Output format, one of: text, json, yaml
`

func describe(ctx context.Context, args []string) error {
Expand Down
9 changes: 4 additions & 5 deletions internal/cmd/export.go → export.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package cmd
package main

import (
"context"
Expand All @@ -11,7 +11,7 @@ import (
)

const exportUsage = `
Usage: timecraft export <resource type> <resource id> <output file>
Usage: timecraft export <resource type> <resource id> <output file> [options]

The export command reads resources from the time machine registry and writes
them to local files. This command is useful to extract data generated by
Expand All @@ -31,12 +31,11 @@ func export(ctx context.Context, args []string) error {
args = parseFlags(flagSet, args)

if len(args) != 3 {
return errors.New(`expected resource type, id, and output file as argument` + useCmd("export"))
return usageError(`Expected resource type, id, and output file as argument` + useCmd("export"))
}

resource, err := findResource("describe", args[0])
if err != nil {
return err
return usageError(err.Error())
}
config, err := loadConfig()
if err != nil {
Expand Down
66 changes: 66 additions & 0 deletions export_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package main_test

import (
"os"
"strings"
"testing"

"github.com/stealthrocket/timecraft/internal/assert"
)

var export = tests{
"show the export command help with the short option": func(t *testing.T) {
stdout, stderr, err := timecraft(t, "export", "-h")
assert.OK(t, err)
assert.HasPrefix(t, stdout, "Usage:\ttimecraft export ")
assert.Equal(t, stderr, "")
},

"show the export command help with the long option": func(t *testing.T) {
stdout, stderr, err := timecraft(t, "export", "--help")
assert.OK(t, err)
assert.HasPrefix(t, stdout, "Usage:\ttimecraft export ")
assert.Equal(t, stderr, "")
},

"export without a resource type": func(t *testing.T) {
stdout, stderr, err := timecraft(t, "export")
assert.ExitError(t, err, 2)
assert.Equal(t, stdout, "")
assert.HasPrefix(t, stderr, "Expected resource type, id, and output file as argument")
},

"export without a resource id": func(t *testing.T) {
stdout, stderr, err := timecraft(t, "export", "profile")
assert.ExitError(t, err, 2)
assert.Equal(t, stdout, "")
assert.HasPrefix(t, stderr, "Expected resource type, id, and output file as argument")
},

"export without an output file": func(t *testing.T) {
stdout, stderr, err := timecraft(t, "export", "profile", "74080192e42e")
assert.ExitError(t, err, 2)
assert.Equal(t, stdout, "")
assert.HasPrefix(t, stderr, "Expected resource type, id, and output file as argument")
},

"export a module to stdout": func(t *testing.T) {
stdout, processID, err := timecraft(t, "run", "./testdata/go/sleep.wasm", "1ns")
assert.OK(t, err)
assert.Equal(t, stdout, "sleeping for 1ns\n")
assert.NotEqual(t, processID, "")

moduleID, stderr, err := timecraft(t, "get", "mod", "-q")
assert.OK(t, err)
assert.Equal(t, stderr, "")
moduleID = strings.TrimSuffix(moduleID, "\n")

moduleData, stderr, err := timecraft(t, "export", "mod", moduleID, "-")
assert.OK(t, err)
assert.Equal(t, stderr, "")

sleepWasm, err := os.ReadFile("./testdata/go/sleep.wasm")
assert.OK(t, err)
assert.True(t, moduleData == string(sleepWasm))
},
}
55 changes: 33 additions & 22 deletions internal/cmd/get.go → get.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
package cmd
package main

import (
"bytes"
"context"
"errors"
"fmt"
"io"
"os"
Expand Down Expand Up @@ -47,14 +46,15 @@ Examples:
Options:
-c, --config Path to the timecraft configuration file (overrides TIMECRAFTCONFIG)
-h, --help Show this usage information
-o, --ouptut format Output format, one of: text, json, yaml
-o, --output format Output format, one of: text, json, yaml
-q, --quiet Only display the resource ids
`

type resource struct {
typ string
alt []string
mediaType format.MediaType
get func(context.Context, io.Writer, *timemachine.Registry) stream.WriteCloser[*format.Descriptor]
get func(context.Context, io.Writer, *timemachine.Registry, bool) stream.WriteCloser[*format.Descriptor]
describe func(context.Context, *timemachine.Registry, string) (any, error)
lookup func(context.Context, *timemachine.Registry, string) (any, error)
}
Expand Down Expand Up @@ -118,18 +118,20 @@ func get(ctx context.Context, args []string) error {
var (
timeRange = timemachine.Since(time.Unix(0, 0))
output = outputFormat("text")
quiet = false
)

flagSet := newFlagSet("timecraft get", getUsage)
customVar(flagSet, &output, "o", "output")
boolVar(flagSet, &quiet, "q", "quiet")
args = parseFlags(flagSet, args)

if len(args) != 1 {
return errors.New(`expected exactly one resource type as argument` + useCmd("get"))
return usageError(`Expected exactly one resource type as argument` + useCmd("get"))
}
resource, err := findResource("get", args[0])
if err != nil {
return err
return usageError(err.Error())
}
config, err := loadConfig()
if err != nil {
Expand All @@ -153,7 +155,7 @@ func get(ctx context.Context, args []string) error {
case "yaml":
writer = yamlprint.NewWriter[*format.Manifest](os.Stdout)
default:
writer = getLogs(ctx, os.Stdout, registry)
writer = getLogs(ctx, os.Stdout, registry, quiet)
}
defer writer.Close()

Expand All @@ -171,22 +173,22 @@ func get(ctx context.Context, args []string) error {
case "yaml":
writer = yamlprint.NewWriter[*format.Descriptor](os.Stdout)
default:
writer = resource.get(ctx, os.Stdout, registry)
writer = resource.get(ctx, os.Stdout, registry, quiet)
}
defer writer.Close()

_, err = stream.Copy[*format.Descriptor](writer, reader)
return err
}

func getConfigs(ctx context.Context, w io.Writer, reg *timemachine.Registry) stream.WriteCloser[*format.Descriptor] {
func getConfigs(ctx context.Context, w io.Writer, reg *timemachine.Registry, quiet bool) stream.WriteCloser[*format.Descriptor] {
type config struct {
ID string `text:"CONFIG ID"`
Runtime string `text:"RUNTIME"`
Modules int `text:"MODULES"`
Size human.Bytes `text:"SIZE"`
}
return newTableWriter(w,
return newTableWriter(w, quiet,
func(c1, c2 config) bool {
return c1.ID < c2.ID
},
Expand All @@ -208,13 +210,13 @@ func getConfigs(ctx context.Context, w io.Writer, reg *timemachine.Registry) str
})
}

func getModules(ctx context.Context, w io.Writer, reg *timemachine.Registry) stream.WriteCloser[*format.Descriptor] {
func getModules(ctx context.Context, w io.Writer, reg *timemachine.Registry, quiet bool) stream.WriteCloser[*format.Descriptor] {
type module struct {
ID string `text:"MODULE ID"`
Name string `text:"MODULE NAME"`
Size human.Bytes `text:"SIZE"`
}
return newTableWriter(w,
return newTableWriter(w, quiet,
func(m1, m2 module) bool {
return m1.ID < m2.ID
},
Expand All @@ -231,12 +233,12 @@ func getModules(ctx context.Context, w io.Writer, reg *timemachine.Registry) str
})
}

func getProcesses(ctx context.Context, w io.Writer, reg *timemachine.Registry) stream.WriteCloser[*format.Descriptor] {
func getProcesses(ctx context.Context, w io.Writer, reg *timemachine.Registry, quiet bool) stream.WriteCloser[*format.Descriptor] {
type process struct {
ID format.UUID `text:"PROCESS ID"`
StartTime human.Time `text:"START"`
}
return newTableWriter(w,
return newTableWriter(w, quiet,
func(p1, p2 process) bool {
return time.Time(p1.StartTime).Before(time.Time(p2.StartTime))
},
Expand All @@ -252,7 +254,7 @@ func getProcesses(ctx context.Context, w io.Writer, reg *timemachine.Registry) s
})
}

func getProfiles(ctx context.Context, w io.Writer, reg *timemachine.Registry) stream.WriteCloser[*format.Descriptor] {
func getProfiles(ctx context.Context, w io.Writer, reg *timemachine.Registry, quiet bool) stream.WriteCloser[*format.Descriptor] {
type profile struct {
ID string `text:"PROFILE ID"`
ProcessID format.UUID `text:"PROCESS ID"`
Expand All @@ -261,7 +263,7 @@ func getProfiles(ctx context.Context, w io.Writer, reg *timemachine.Registry) st
Duration human.Duration `text:"DURATION"`
Size human.Bytes `text:"SIZE"`
}
return newTableWriter(w,
return newTableWriter(w, quiet,
func(p1, p2 profile) bool {
if p1.ProcessID != p2.ProcessID {
return bytes.Compare(p1.ProcessID[:], p2.ProcessID[:]) < 0
Expand Down Expand Up @@ -289,13 +291,13 @@ func getProfiles(ctx context.Context, w io.Writer, reg *timemachine.Registry) st
})
}

func getRuntimes(ctx context.Context, w io.Writer, reg *timemachine.Registry) stream.WriteCloser[*format.Descriptor] {
func getRuntimes(ctx context.Context, w io.Writer, reg *timemachine.Registry, quiet bool) stream.WriteCloser[*format.Descriptor] {
type runtime struct {
ID string `text:"RUNTIME ID"`
Runtime string `text:"RUNTIME NAME"`
Version string `text:"VERSION"`
}
return newTableWriter(w,
return newTableWriter(w, quiet,
func(r1, r2 runtime) bool {
return r1.ID < r2.ID
},
Expand All @@ -312,14 +314,14 @@ func getRuntimes(ctx context.Context, w io.Writer, reg *timemachine.Registry) st
})
}

func getLogs(ctx context.Context, w io.Writer, reg *timemachine.Registry) stream.WriteCloser[*format.Manifest] {
func getLogs(ctx context.Context, w io.Writer, reg *timemachine.Registry, quiet bool) stream.WriteCloser[*format.Manifest] {
type manifest struct {
ProcessID format.UUID `text:"PROCESS ID"`
Segments human.Count `text:"SEGMENTS"`
StartTime human.Time `text:"START"`
Size human.Bytes `text:"SIZE"`
}
return newTableWriter(w,
return newTableWriter(w, quiet,
func(m1, m2 manifest) bool {
return time.Time(m1.StartTime).Before(time.Time(m2.StartTime))
},
Expand All @@ -336,8 +338,17 @@ func getLogs(ctx context.Context, w io.Writer, reg *timemachine.Registry) stream
})
}

func newTableWriter[T1, T2 any](w io.Writer, orderBy func(T1, T1) bool, conv func(T2) (T1, error)) stream.WriteCloser[T2] {
tw := textprint.NewTableWriter[T1](w, textprint.OrderBy(orderBy))
func newTableWriter[T1, T2 any](w io.Writer, quiet bool, orderBy func(T1, T1) bool, conv func(T2) (T1, error)) stream.WriteCloser[T2] {
opts := []textprint.TableOption[T1]{
textprint.OrderBy(orderBy),
}
if quiet {
opts = append(opts,
textprint.Header[T1](false),
textprint.List[T1](true),
)
}
tw := textprint.NewTableWriter[T1](w, opts...)
cw := stream.ConvertWriter[T1](tw, conv)
return stream.NewWriteCloser(cw, tw)
}
Expand Down
Loading
Loading