Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add file generator #37

Closed
wants to merge 2 commits into from
Closed
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
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,18 @@ one repeat
one repeat
```

The `file` generator can be use to read custom values from a file:

```sh
$ printf "one\ntwo\nthree" > values.txt
$ fakedata -l5 file,values.txt
three
two
two
one
two
```

# Formatters

### SQL formatter
Expand All @@ -123,7 +135,7 @@ $ fakedata --format=sql --limit 1 email domain
INSERT INTO TABLE (email,domain) values ('yigitpinar@example.org','example.me');
```

You can specify the name of the column using a field with the following format
You can specify the name of the column using a field with the following format
`column_name=generator`:

```sh
Expand Down
134 changes: 111 additions & 23 deletions integration/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,21 @@ import (
"io/ioutil"
"os"
"os/exec"
"path"
"path/filepath"
"runtime"
"testing"

"reflect"
"strings"

"github.com/kr/pretty"
)

var update = flag.Bool("update", false, "update golden files")

var binaryName = "fakedata"
const binaryName = "fakedata"

var binaryPath string

func diff(expected, actual interface{}) []string {
return pretty.Diff(expected, actual)
Expand Down Expand Up @@ -54,49 +56,130 @@ func TestCliArgs(t *testing.T) {
name string
args []string
fixture string
match func(string, string) bool
}{
{"no arguments", []string{}, "help.golden"},
{"list generators", []string{"-g"}, "generators.golden"},
{"default format", []string{"int,42..42", "enum,foo..foo"}, "default-format.golden"},
{"unknown generators", []string{"madeupgenerator", "anothermadeupgenerator"}, "unknown-generators.golden"},
{"default format with limit short", []string{"-l=5", "int,42..42", "enum,foo..foo"}, "default-format-with-limit.golden"},
{"default format with limit", []string{"--limit=5", "int,42..42", "enum,foo..foo"}, "default-format-with-limit.golden"},
{"csv format short", []string{"-f=csv", "int,42..42", "enum,foo..foo"}, "csv-format.golden"},
{"csv format", []string{"--format=csv", "int,42..42", "enum,foo..foo"}, "csv-format.golden"},
{"tab format", []string{"-f=tab", "int,42..42", "enum,foo..foo"}, "tab-format.golden"},
{"sql format", []string{"-f=sql", "int,42..42", "enum,foo..foo"}, "sql-format.golden"},
{"sql format with keys", []string{"-f=sql", "age=int,42..42", "name=enum,foo..foo"}, "sql-format-with-keys.golden"},
{"sql format with table name", []string{"-f=sql", "-t=USERS", "int,42..42", "enum,foo..foo"}, "sql-format-with-table-name.golden"},
{
name: "no arguments",
args: []string{},
fixture: "help.golden",
},
{
name: "list generators",
args: []string{"-g"},
fixture: "generators.golden",
},
{
name: "default format",
args: []string{"int,42..42", "enum,foo..foo"},
fixture: "default-format.golden",
},
{
name: "unknown generators",
args: []string{"madeupgenerator", "anothermadeupgenerator"},
fixture: "unknown-generators.golden",
},
{
name: "default format with limit short",
args: []string{"-l=5", "int,42..42", "enum,foo..foo"},
fixture: "default-format-with-limit.golden",
},
{
name: "default format with limit",
args: []string{"--limit=5", "int,42..42", "enum,foo..foo"},
fixture: "default-format-with-limit.golden",
},
{
name: "csv format short",
args: []string{"-f=csv", "int,42..42", "enum,foo..foo"},
fixture: "csv-format.golden",
},
{
name: "csv format",
args: []string{"--format=csv", "int,42..42", "enum,foo..foo"},
fixture: "csv-format.golden",
},
{
name: "tab format",
args: []string{"-f=tab", "int,42..42", "enum,foo..foo"},
fixture: "tab-format.golden",
},
{
name: "sql format",
args: []string{"-f=sql", "int,42..42", "enum,foo..foo"},
fixture: "sql-format.golden",
},
{
name: "sql format with keys",
args: []string{"-f=sql", "age=int,42..42", "name=enum,foo..foo"},
fixture: "sql-format-with-keys.golden",
},
{
name: "sql format with table name",
args: []string{"-f=sql", "-t=USERS", "int,42..42", "enum,foo..foo"},
fixture: "sql-format-with-table-name.golden",
},
{
name: "file generator",
args: []string{"file,integration/file.golden"},
fixture: "file.golden",
match: func(actual, expected string) bool {
for _, line := range strings.Split(actual, "\n") {
if !strings.Contains(expected+"\n", line+"\n") {
return false
}
}
return true
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
dir, err := os.Getwd()
if err != nil {
t.Fatal(err)
}

cmd := exec.Command(path.Join(dir, binaryName), tt.args...)
cmd := exec.Command(binaryPath, tt.args...)
output, err := cmd.CombinedOutput()
if err != nil {
t.Fatal(err)
t.Fatal(err, "\n", string(output))
}

if *update {
writeFixture(t, tt.fixture, output)
}

actual := string(output)

expected := loadFixture(t, tt.fixture)

if !reflect.DeepEqual(actual, expected) {
if tt.match != nil {
if !tt.match(actual, expected) {
t.Fatalf("values do not match: \n%v\n%v", actual, expected)
}
} else if !reflect.DeepEqual(actual, expected) {
t.Fatalf("diff: %v", diff(expected, actual))
}
})
}
}

func TestCliErr(t *testing.T) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These don't fit into the table above.

tests := []struct {
name string
args []string
}{
{"no file", []string{"file"}},
{"no file,", []string{"file,"}},
{"no file,''", []string{"file,''"}},
{`no file,""`, []string{`file,""`}},
{"file does not exist", []string{`file,'this file does not exist.txt'`}},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := exec.Command(binaryPath, tt.args...).Run(); err == nil {
t.Fatalf("expected to fail with args: %v", tt.args)
}
})
}
}

func TestMain(m *testing.M) {
err := os.Chdir("..")
if err != nil {
Expand All @@ -109,6 +192,11 @@ func TestMain(m *testing.M) {
fmt.Printf("could not make binary for %s: %v", binaryName, err)
os.Exit(1)
}
binaryPath, err = filepath.Abs(binaryName)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only need to get the path once.

if err != nil {
fmt.Printf("could not get binary path: %v", err)
os.Exit(1)
}

os.Exit(m.Run())
}
10 changes: 10 additions & 0 deletions integration/file.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
one
two
three
four
five
six
seven
eight
nine
ten
1 change: 1 addition & 0 deletions integration/generators.golden
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ double double number
email email
enum a random value from an enum. Defaults to "foo..bar..baz"
event.action clicked|purchased|viewed|watched
file Read a random line from a file. Pass filepath with 'file,path/to/file.txt'.
http.method DELETE|GET|HEAD|OPTION|PATCH|POST|PUT
int positive integer. Accepts range min..max (default: 1..1000).
ipv4 ipv4
Expand Down
28 changes: 28 additions & 0 deletions pkg/fakedata/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package fakedata

import (
"fmt"
"io/ioutil"
"log"
"math/rand"
"sort"
Expand Down Expand Up @@ -177,6 +178,27 @@ var enum = func(column Column) string {
return withEnum(enum)(column)
}

var fileCache map[string][]string
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice! As the program works right now I think this is a cool approach.

It's interesting to notice that this is needed because the design of the program is still suboptimal. Right now, all the Constraints are computed for every single row which makes no sense from a perf perspective :D. I've been wanting to start working on it for a while (but only because it's fun!) but I had no real reason. Now that I see this I feel more compelled to start working on that too.

It was only a digression though (kind of a way for me to start thinking about this more concretely). I think what you did here is pretty good for the current design

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lucapette sure, while adding this I was wondering about the same thing :D


var file = func(column Column) string {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lucapette is there a reason you put the functions in vars instead of the usual func file() ...?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's funny. I think it's literally "historical reasons" even though the project is so young. I started it off an existing codebase and evolved the design in a direction I liked more. So long story short, there's no good reason right now. I'll put in a small gardening/good first patch issue for it.

I think it's good we keep it consistent right now though so I appreciate you followed existing style.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#38

p := column.Constraints
if p == "" {
log.Fatalf("%s: no file path given", column.Name)
}
// Try to get file from cache
f := fileCache[p]
// Load file if cache is empty
if f == nil {
content, err := ioutil.ReadFile(p)
if err != nil {
log.Fatalf("%s: no readable file found at '%s'", column.Name, p)
}
f = strings.Split(string(content), "\n")
}
// Return random line
return f[rand.Intn(len(f))]
}

func init() {
generators = make(map[string]Generator)

Expand Down Expand Up @@ -332,4 +354,10 @@ func init() {
Desc: `a random value from an enum. Defaults to "foo..bar..baz"`,
Func: enum,
}

generators["file"] = Generator{
Name: "file",
Desc: `Read a random line from a file. Pass filepath with 'file,path/to/file.txt'.`,
Func: file,
}
}