Files
2026-07-13 12:32:14 +08:00

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
}