422 lines
10 KiB
Go
422 lines
10 KiB
Go
package fsql
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/sha1"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
var files = map[string]*os.FileInfo{}
|
|
|
|
func TestRun_All(t *testing.T) {
|
|
type Case struct {
|
|
query string
|
|
expected string
|
|
}
|
|
|
|
cases := []Case{
|
|
{
|
|
query: "SELECT all FROM ./testdata WHERE name = foo",
|
|
expected: fmt.Sprintf("%s\t-------\tfoo\n",
|
|
strings.Join(GetAttrs("foo", "mode", "size", "time"), "\t")),
|
|
},
|
|
{
|
|
query: "SELECT all FROM ./testdata WHERE name LIKE waldo",
|
|
expected: fmt.Sprintf("%s\twaldo\n",
|
|
strings.Join(GetAttrs("foo/quuz/waldo", "mode", "size", "time", "hash"), "\t")),
|
|
},
|
|
{
|
|
query: "SELECT all FROM ./testdata WHERE FORMAT(time, 'Jan 02 2006 15:04') > 'Jan 01 2999 00:00'",
|
|
expected: "",
|
|
},
|
|
{
|
|
query: "SELECT all FROM ./testdata WHERE mode IS DIR",
|
|
expected: fmt.Sprintf(
|
|
strings.Repeat("%s\n", 8),
|
|
fmt.Sprintf(
|
|
"%s\t-------\t%-8s",
|
|
strings.Join(GetAttrs(".", "mode", "size", "time"), "\t"),
|
|
"testdata",
|
|
),
|
|
fmt.Sprintf(
|
|
"%s\t-------\t%-8s",
|
|
strings.Join(GetAttrs("bar", "mode", "size", "time"), "\t"),
|
|
"bar",
|
|
),
|
|
fmt.Sprintf(
|
|
"%s\t-------\t%-8s",
|
|
strings.Join(GetAttrs("bar/garply", "mode", "size", "time"), "\t"),
|
|
"garply",
|
|
),
|
|
fmt.Sprintf(
|
|
"%s\t-------\t%-8s",
|
|
strings.Join(GetAttrs("bar/garply/xyzzy", "mode", "size", "time"), "\t"),
|
|
"xyzzy",
|
|
),
|
|
fmt.Sprintf(
|
|
"%s\t-------\t%-8s",
|
|
strings.Join(GetAttrs("bar/garply/xyzzy/thud", "mode", "size", "time"), "\t"),
|
|
"thud",
|
|
),
|
|
fmt.Sprintf(
|
|
"%s\t-------\t%-8s",
|
|
strings.Join(GetAttrs("foo", "mode", "size", "time"), "\t"),
|
|
"foo",
|
|
),
|
|
fmt.Sprintf(
|
|
"%s\t-------\t%-8s",
|
|
strings.Join(GetAttrs("foo/quuz", "mode", "size", "time"), "\t"),
|
|
"quuz",
|
|
),
|
|
fmt.Sprintf(
|
|
"%s\t-------\t%-8s",
|
|
strings.Join(GetAttrs("foo/quuz/fred", "mode", "size", "time"), "\t"),
|
|
"fred",
|
|
),
|
|
),
|
|
},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
actual := DoRun(c.query)
|
|
if !reflect.DeepEqual(c.expected, actual) {
|
|
t.Fatalf("%s\nExpected:\n%v\nGot:\n%v", c.query, c.expected, actual)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRun_Multiple(t *testing.T) {
|
|
type Case struct {
|
|
query string
|
|
expected string
|
|
}
|
|
|
|
cases := []Case{
|
|
{
|
|
query: "SELECT name, size FROM ./testdata WHERE name = foo",
|
|
expected: fmt.Sprintf("foo\t%s\n", GetAttrs("foo", "size")[0]),
|
|
},
|
|
{
|
|
query: "SELECT size, name FROM ./testdata WHERE name = foo",
|
|
expected: fmt.Sprintf("%s\tfoo\n", GetAttrs("foo", "size")[0]),
|
|
},
|
|
{
|
|
query: "SELECT FULLPATH(name), size, time FROM ./testdata/foo",
|
|
expected: fmt.Sprintf(
|
|
strings.Repeat("%s\n", 7),
|
|
fmt.Sprintf(
|
|
"%-31s\t%s",
|
|
"testdata/foo",
|
|
strings.Join(GetAttrs("foo", "size", "time"), "\t"),
|
|
),
|
|
fmt.Sprintf(
|
|
"%-31s\t%s",
|
|
"testdata/foo/quux",
|
|
strings.Join(GetAttrs("foo/quux", "size", "time"), "\t"),
|
|
),
|
|
fmt.Sprintf(
|
|
"%-31s\t%s",
|
|
"testdata/foo/quuz",
|
|
strings.Join(GetAttrs("foo/quuz", "size", "time"), "\t"),
|
|
),
|
|
fmt.Sprintf(
|
|
"%-31s\t%s",
|
|
"testdata/foo/quuz/fred",
|
|
strings.Join(GetAttrs("foo/quuz/fred", "size", "time"), "\t"),
|
|
),
|
|
fmt.Sprintf(
|
|
"%-31s\t%s",
|
|
"testdata/foo/quuz/fred/.gitkeep",
|
|
strings.Join(GetAttrs("foo/quuz/fred/.gitkeep", "size", "time"), "\t"),
|
|
),
|
|
fmt.Sprintf(
|
|
"%-31s\t%s",
|
|
"testdata/foo/quuz/waldo",
|
|
strings.Join(GetAttrs("foo/quuz/waldo", "size", "time"), "\t"),
|
|
),
|
|
fmt.Sprintf(
|
|
"%-31s\t%s",
|
|
"testdata/foo/qux",
|
|
strings.Join(GetAttrs("foo/qux", "size", "time"), "\t"),
|
|
),
|
|
),
|
|
},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
actual := DoRun(c.query)
|
|
if !reflect.DeepEqual(c.expected, actual) {
|
|
t.Fatalf("%s\nExpected:\n%v\nGot:\n%v", c.query, c.expected, actual)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRun_Name(t *testing.T) {
|
|
type Case struct {
|
|
query string
|
|
expected string
|
|
}
|
|
|
|
cases := []Case{
|
|
{
|
|
query: "SELECT name FROM ./testdata WHERE name REGEXP ^g.*",
|
|
expected: "garply\ngrault\n",
|
|
},
|
|
{
|
|
query: "SELECT FULLPATH(name) FROM ./testdata WHERE name REGEXP ^b.*",
|
|
expected: "testdata/bar\ntestdata/baz\n",
|
|
},
|
|
{
|
|
query: "SELECT UPPER(FULLPATH(name)) FROM ./testdata WHERE mode IS DIR",
|
|
expected: fmt.Sprintf(
|
|
strings.Repeat("%-30s\n", 8),
|
|
"TESTDATA",
|
|
"TESTDATA/BAR",
|
|
"TESTDATA/BAR/GARPLY",
|
|
"TESTDATA/BAR/GARPLY/XYZZY",
|
|
"TESTDATA/BAR/GARPLY/XYZZY/THUD",
|
|
"TESTDATA/FOO",
|
|
"TESTDATA/FOO/QUUZ",
|
|
"TESTDATA/FOO/QUUZ/FRED",
|
|
),
|
|
},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
actual := DoRun(c.query)
|
|
if !reflect.DeepEqual(c.expected, actual) {
|
|
t.Fatalf("%s\nExpected:\n%v\nGot:\n%v", c.query, c.expected, actual)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRun_Size(t *testing.T) {
|
|
type Case struct {
|
|
query string
|
|
expected string
|
|
}
|
|
|
|
cases := []Case{
|
|
{
|
|
query: "SELECT size FROM ./testdata WHERE name = foo",
|
|
expected: fmt.Sprintf("%s\n", GetAttrs("foo", "size")[0]),
|
|
},
|
|
{
|
|
query: "SELECT FORMAT(size, KB) FROM ./testdata WHERE name = foo",
|
|
expected: fmt.Sprintf("%s\n", GetAttrs("foo", "size:kb")[0]),
|
|
},
|
|
{
|
|
query: "SELECT FORMAT(size, MB) FROM ./testdata WHERE name = foo",
|
|
expected: fmt.Sprintf("%s\n", GetAttrs("foo", "size:mb")[0]),
|
|
},
|
|
{
|
|
query: "SELECT FORMAT(size, GB) FROM ./testdata WHERE name = foo",
|
|
expected: fmt.Sprintf("%s\n", GetAttrs("foo", "size:gb")[0]),
|
|
},
|
|
{
|
|
query: "SELECT size FROM ./testdata WHERE name LIKE qu",
|
|
expected: fmt.Sprintf(
|
|
strings.Repeat("%s\n", 3),
|
|
GetAttrs("foo/quux", "size")[0],
|
|
GetAttrs("foo/quuz", "size")[0],
|
|
GetAttrs("foo/qux", "size")[0],
|
|
),
|
|
},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
actual := DoRun(c.query)
|
|
if !reflect.DeepEqual(c.expected, actual) {
|
|
t.Fatalf("%s\nExpected:\n%v\nGot:\n%v", c.query, c.expected, actual)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRun_Time(t *testing.T) {
|
|
type Case struct {
|
|
query string
|
|
expected string
|
|
}
|
|
|
|
cases := []Case{
|
|
{
|
|
query: "SELECT time FROM ./testdata WHERE name = baz",
|
|
expected: fmt.Sprintf("%s\n", GetAttrs("baz", "time")[0]),
|
|
},
|
|
{
|
|
query: "SELECT FORMAT(time, ISO) FROM ./testdata WHERE name = foo",
|
|
expected: fmt.Sprintf("%s\n", GetAttrs("foo", "time:iso")[0]),
|
|
},
|
|
{
|
|
query: "SELECT FORMAT(time, 2006) FROM ./testdata WHERE NOT name LIKE .%",
|
|
expected: strings.Repeat(fmt.Sprintf("%s\n", GetAttrs(".", "time:year")[0]), 14),
|
|
},
|
|
{
|
|
query: "SELECT time FROM ./testdata/foo/quuz",
|
|
expected: fmt.Sprintf(
|
|
strings.Repeat("%s\n", 4),
|
|
GetAttrs("foo/quuz", "time")[0],
|
|
GetAttrs("foo/quuz/fred", "time")[0],
|
|
GetAttrs("foo/quuz/fred/.gitkeep", "time")[0],
|
|
GetAttrs("foo/quuz/waldo", "time")[0],
|
|
),
|
|
},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
actual := DoRun(c.query)
|
|
if !reflect.DeepEqual(c.expected, actual) {
|
|
t.Fatalf("%s\nExpected:\n%v\nGot:\n%v", c.query, c.expected, actual)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRun_Mode(t *testing.T) {
|
|
type Case struct {
|
|
query string
|
|
expected string
|
|
}
|
|
|
|
cases := []Case{
|
|
{
|
|
query: "SELECT mode FROM ./testdata WHERE name = foo",
|
|
expected: fmt.Sprintf("%s\n", GetAttrs("foo", "mode")[0]),
|
|
},
|
|
{
|
|
query: "SELECT mode FROM ./testdata WHERE name = baz",
|
|
expected: fmt.Sprintf("%s\n", GetAttrs("baz", "mode")[0]),
|
|
},
|
|
{
|
|
query: "SELECT mode FROM ./testdata WHERE mode IS DIR",
|
|
expected: fmt.Sprintf(strings.Repeat("%s\n", 8),
|
|
GetAttrs("", "mode")[0],
|
|
GetAttrs("foo", "mode")[0],
|
|
GetAttrs("foo/quuz", "mode")[0],
|
|
GetAttrs("foo/quuz/fred", "mode")[0],
|
|
GetAttrs("bar", "mode")[0],
|
|
GetAttrs("bar/garply", "mode")[0],
|
|
GetAttrs("bar/garply/xyzzy", "mode")[0],
|
|
GetAttrs("bar/garply/xyzzy/thud", "mode")[0],
|
|
),
|
|
},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
actual := DoRun(c.query)
|
|
if !reflect.DeepEqual(c.expected, actual) {
|
|
t.Fatalf("%s\nExpected:\n\"%v\"\nGot:\n\"%v\"", c.query, c.expected, actual)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRun_Hash(t *testing.T) {
|
|
type Case struct {
|
|
query string
|
|
expected string
|
|
}
|
|
|
|
// TODO
|
|
cases := []Case{}
|
|
|
|
for _, c := range cases {
|
|
actual := DoRun(c.query)
|
|
if !reflect.DeepEqual(c.expected, actual) {
|
|
t.Fatalf("%s\nExpected:\n%v\nGot:\n%v", c.query, c.expected, actual)
|
|
}
|
|
}
|
|
}
|
|
|
|
func GetAttrs(path string, attrs ...string) []string {
|
|
// If the files map is empty, walk ./testdata and populate it.
|
|
if len(files) == 0 {
|
|
if err := filepath.Walk(
|
|
"./testdata",
|
|
func(path string, info os.FileInfo, err error) error {
|
|
files[filepath.Clean(path)] = &info
|
|
return nil
|
|
},
|
|
); err != nil {
|
|
return []string{}
|
|
}
|
|
}
|
|
|
|
path = filepath.Clean(fmt.Sprintf("testdata/%s", path))
|
|
file, ok := files[path]
|
|
if !ok {
|
|
return []string{}
|
|
}
|
|
|
|
result := make([]string, len(attrs))
|
|
for i, attr := range attrs {
|
|
// Hard-coding modifiers works for the time being, but we might need a more
|
|
// elegant solution when we introduce new modifiers in the future.
|
|
switch attr {
|
|
case "hash":
|
|
b, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return []string{}
|
|
}
|
|
h := sha1.New()
|
|
if _, err := h.Write(b); err != nil {
|
|
return []string{}
|
|
}
|
|
result[i] = hex.EncodeToString(h.Sum(nil))[:7]
|
|
case "mode":
|
|
result[i] = (*file).Mode().String()
|
|
case "size":
|
|
result[i] = fmt.Sprintf("%d", (*file).Size())
|
|
case "size:kb", "size:mb", "size:gb":
|
|
size := (*file).Size()
|
|
switch attr[len(attr)-2:] {
|
|
case "kb":
|
|
result[i] = fmt.Sprintf("%fkb", float64(size)/(1<<10))
|
|
case "mb":
|
|
result[i] = fmt.Sprintf("%fmb", float64(size)/(1<<20))
|
|
case "gb":
|
|
result[i] = fmt.Sprintf("%fgb", float64(size)/(1<<30))
|
|
}
|
|
case "time":
|
|
result[i] = (*file).ModTime().Format(time.Stamp)
|
|
case "time:iso":
|
|
result[i] = (*file).ModTime().Format(time.RFC3339)
|
|
case "time:year":
|
|
result[i] = (*file).ModTime().Format("2006")
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
// DoRun executes fsql.Run and returns the output.
|
|
func DoRun(query string) string {
|
|
stdout := os.Stdout
|
|
ch := make(chan string)
|
|
|
|
r, w, err := os.Pipe()
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
os.Stdout = w
|
|
|
|
if err := Run(query); err != nil {
|
|
return ""
|
|
}
|
|
|
|
go func() {
|
|
var buf bytes.Buffer
|
|
io.Copy(&buf, r)
|
|
ch <- buf.String()
|
|
}()
|
|
|
|
w.Close()
|
|
os.Stdout = stdout
|
|
return <-ch
|
|
}
|