From b5685a518effc0e97df8aa52cbff601b5fd7632d Mon Sep 17 00:00:00 2001 From: Argonui Date: Thu, 10 Nov 2022 09:00:14 -0600 Subject: [PATCH 1/2] Ensure correct rotational rounding This change preserves the enforcement of positive rotation, without using absolute value --- objects/numbersmoother.go | 6 +++--- objects/numbersmoother_test.go | 18 +++++++++++++++++- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/objects/numbersmoother.go b/objects/numbersmoother.go index 3269125..07b87d1 100644 --- a/objects/numbersmoother.go +++ b/objects/numbersmoother.go @@ -6,8 +6,8 @@ import ( var ( // because arrays can't be constant - posRounded = []string{"posX", "posY", "posZ"} - rotRounded = []string{"rotX", "rotY", "rotZ"} + posRounded = []string{"posX", "posY", "posZ"} + rotRounded = []string{"rotX", "rotY", "rotZ"} colorRounded = []string{"b", "g", "r", "a"} ) @@ -33,7 +33,7 @@ func Smooth(objraw interface{}) interface{} { for _, key := range rotRounded { if val, ok := obj[key]; ok { if fl, ok := val.(float64); ok { - obj[key] = math.Abs(math.Mod(roundFloat(fl, 0), 360)) + obj[key] = math.Mod(roundFloat(fl, 0)+360, 360) } } } diff --git a/objects/numbersmoother_test.go b/objects/numbersmoother_test.go index a02b2b6..772a05c 100644 --- a/objects/numbersmoother_test.go +++ b/objects/numbersmoother_test.go @@ -40,6 +40,23 @@ func TestDegree(t *testing.T) { } } +func TestDegreeAbs(t *testing.T) { + j := map[string]interface{}{ + "rotX": float64(370), + "rotY": -89.83327, + "rotZ": float64(-0.004), + } + got := Smooth(j) + want := map[string]interface{}{ + "rotX": float64(10), + "rotY": float64(270), + "rotZ": float64(0), + } + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("want != got:\n%v\n", diff) + } +} + func TestColor(t *testing.T) { j := map[string]interface{}{ "r": 0.42513, @@ -77,4 +94,3 @@ func TestColorTransparent(t *testing.T) { t.Errorf("want != got:\n%v\n", diff) } } - From 8dd595d74122ee3cb27d7023ae3776748e402fb8 Mon Sep 17 00:00:00 2001 From: Argonui Date: Thu, 10 Nov 2022 13:20:20 -0600 Subject: [PATCH 2/2] Smooth snap points and alt views At the same time this change necessitated a re-vamp of the fake files struct --- mod/reverse.go | 11 ++- mod/reverse_test.go | 161 +++++++++++++++++++++++++++++++++ objects/functional_test.go | 95 +++++++++++++++++-- objects/numbersmoother.go | 83 ++++++++++++++++- objects/numbersmoother_test.go | 53 ++++++++++- objects/objects.go | 21 ++++- objects/objects_test.go | 161 ++++----------------------------- tests/e2e_test.go | 99 +------------------- tests/fakefiles.go | 125 +++++++++++++++++++++++++ tests/fakefiles_test.go | 67 ++++++++++++++ 10 files changed, 616 insertions(+), 260 deletions(-) create mode 100644 mod/reverse_test.go create mode 100644 tests/fakefiles.go create mode 100644 tests/fakefiles_test.go diff --git a/mod/reverse.go b/mod/reverse.go index 27d50a9..9e7c559 100644 --- a/mod/reverse.go +++ b/mod/reverse.go @@ -87,9 +87,16 @@ func (r *Reverser) Write(raw map[string]interface{}) error { if err != nil { return fmt.Errorf("mismatch expectations in key %s : %v", objKey, err) } - + if objKey == "SnapPoints" { + smoothed, err := objects.SmoothSnapPoints(arr) + if err != nil { + return fmt.Errorf("SmoothSnapPoints(): %v", err) + } + arr = smoothed + } // decide if creating a separate file is worth it if len(fmt.Sprint(arr)) < 200 { + raw[objKey] = arr continue } @@ -129,7 +136,7 @@ func convertToObjArray(v interface{}) ([]map[string]interface{}, error) { rawArr, ok := v.([]interface{}) if !ok { - return nil, fmt.Errorf("%v is not an array", v) + return nil, fmt.Errorf("%v is not an array, is %T", v, v) } for _, rv := range rawArr { diff --git a/mod/reverse_test.go b/mod/reverse_test.go new file mode 100644 index 0000000..fd19c44 --- /dev/null +++ b/mod/reverse_test.go @@ -0,0 +1,161 @@ +package mod + +import ( + "ModCreator/tests" + "ModCreator/types" + "testing" + + "github.com/google/go-cmp/cmp" +) + +func TestReverse(t *testing.T) { + for _, tc := range []struct { + name string + input map[string]interface{} + wantRootConfig map[string]interface{} + wantModSettings map[string]types.J + wantObjs map[string]types.J + wantObjTexts map[string]string + }{ + { + name: "SnapPoints", + input: map[string]interface{}{ + "SnapPoints": []interface{}{ + map[string]interface{}{ + "Position": map[string]interface{}{ + "x": float64(12.123456), + "y": float64(22.123456), + "z": float64(32.123456), + }, + }, + }, + }, + wantRootConfig: map[string]interface{}{ + "SnapPoints": []interface{}{ + map[string]interface{}{ + "Position": map[string]interface{}{ + "x": float64(12.123), + "y": float64(22.123), + "z": float64(32.123), + }, + }, + }, + }, + wantModSettings: map[string]types.J{}, + }, + { + name: "SnapPointsOwnFile", + input: map[string]interface{}{ + "SnapPoints": []interface{}{ + map[string]interface{}{ + "Position": map[string]interface{}{ + "x": float64(12.123456), + "y": float64(22.123456), + "z": float64(32.123456), + }, + }, + map[string]interface{}{ + "Position": map[string]interface{}{ + "x": float64(12.123456), + "y": float64(22.123456), + "z": float64(32.123456), + }, + }, + map[string]interface{}{ + "Position": map[string]interface{}{ + "x": float64(12.123456), + "y": float64(22.123456), + "z": float64(32.123456), + }, + }, + map[string]interface{}{ + "Position": map[string]interface{}{ + "x": float64(12.123456), + "y": float64(22.123456), + "z": float64(32.123456), + }, + }, + map[string]interface{}{ + "Position": map[string]interface{}{ + "x": float64(12.123456), + "y": float64(22.123456), + "z": float64(32.123456), + }, + }, + }, + }, + wantRootConfig: map[string]interface{}{ + "SnapPoints_path": "SnapPoints.json", + }, + wantModSettings: map[string]types.J{ + "SnapPoints.json": types.J{ + "testarray": []map[string]interface{}{ // implementation detail of fake files + map[string]interface{}{ + "Position": types.J{ + "x": float64(12.123), + "y": float64(22.123), + "z": float64(32.123), + }, + }, + map[string]interface{}{ + "Position": types.J{ + "x": float64(12.123), + "y": float64(22.123), + "z": float64(32.123), + }, + }, + map[string]interface{}{ + "Position": types.J{ + "x": float64(12.123), + "y": float64(22.123), + "z": float64(32.123), + }, + }, + map[string]interface{}{ + "Position": types.J{ + "x": float64(12.123), + "y": float64(22.123), + "z": float64(32.123), + }, + }, + map[string]interface{}{ + "Position": types.J{ + "x": float64(12.123), + "y": float64(22.123), + "z": float64(32.123), + }, + }, + }, + }, + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + finalOutput := tests.NewFF() + modsettings := tests.NewFF() + objsAndLua := tests.NewFF() + r := Reverser{ + ModSettingsWriter: modsettings, + LuaWriter: objsAndLua, + ObjWriter: objsAndLua, + ObjDirCreeator: objsAndLua, + RootWrite: finalOutput, + } + err := r.Write(tc.input) + if err != nil { + t.Fatalf("Error reversing : %v", err) + } + got, err := finalOutput.ReadObj("config.json") + if err != nil { + t.Fatalf("Error reading final config.json : %v", err) + } + if diff := cmp.Diff(tc.wantRootConfig, got); diff != "" { + t.Errorf("want != got:\n%v\n", diff) + } + if diff := cmp.Diff(tc.wantModSettings, modsettings.Data); diff != "" { + t.Errorf("want != got:\n%v\n", diff) + } + }) + } + +} diff --git a/objects/functional_test.go b/objects/functional_test.go index 4e9746b..7ec6b89 100644 --- a/objects/functional_test.go +++ b/objects/functional_test.go @@ -1,6 +1,7 @@ package objects import ( + "ModCreator/tests" "ModCreator/types" "testing" @@ -77,9 +78,9 @@ func TestFileToJson(t *testing.T) { }, }, } { - ff := &fakeFiles{ - fs: tc.txtfiles, - data: tc.jsonfiles, + ff := &tests.FakeFiles{ + Fs: tc.txtfiles, + Data: tc.jsonfiles, } o := objConfig{} err := o.parseFromFile("foo/cool.123456.json", ff) @@ -116,6 +117,85 @@ func TestJsonToFiles(t *testing.T) { }, }, wantTxtfiles: map[string]string{}, + }, { + relpath: "objrounding", + input: types.J{ + "GUID": "123", + "foobar": "baz", + "AltLookAngle": types.J{ + "x": 0.123456789, + "y": -0.123456789, + "z": float64(370), + }, + "AttachedSnapPoints": []map[string]interface{}{ + { + "Position": types.J{ + "x": -1.82239926, + "y": 0.100342259, + "z": 0.6163123, + }, + "Rotation": types.J{ + "x": 3.36023078e-7, + "y": 0.008230378, + "z": -2.29263165e-7, + }, + }, + { + "Rotation": types.J{ + "x": 3.36023078e-7, + "y": 0.008230378, + "z": -2.29263165e-7, + }, + }, + { + "Position": types.J{ + "x": -1.82239926, + "y": 0.100342259, + "z": 0.6163123, + }, + }, + }, + }, + wantJsonfiles: map[string]types.J{ + "objrounding/123.json": types.J{ + "GUID": "123", + "foobar": "baz", + "AltLookAngle": types.J{ + "x": float64(0), + "y": float64(0), + "z": float64(10), + }, + "AttachedSnapPoints": []map[string]interface{}{ + { + "Position": types.J{ + "x": float64(-1.822), + "y": float64(0.100), + "z": float64(0.616), + }, + "Rotation": types.J{ + "x": float64(0), + "y": float64(0), + "z": float64(0), + }, + }, + { + "Rotation": types.J{ + "x": float64(0), + "y": float64(0), + "z": float64(0), + }, + }, + { + "Position": types.J{ + "x": -1.822, + "y": 0.100, + "z": 0.616, + }, + }, + }, + }, + }, + wantTxtfiles: map[string]string{}, }, { relpath: "foo2", input: types.J{ @@ -168,10 +248,7 @@ func TestJsonToFiles(t *testing.T) { wantTxtfiles: map[string]string{}, }, } { - ff := &fakeFiles{ - fs: map[string]string{}, - data: map[string]types.J{}, - } + ff := tests.NewFF() o := objConfig{} err := o.parseFromJSON(tc.input) if err != nil { @@ -181,10 +258,10 @@ func TestJsonToFiles(t *testing.T) { if err != nil { t.Fatalf("printToFile(%s): %v", o.getAGoodFileName(), err) } - if diff := cmp.Diff(tc.wantJsonfiles, ff.data); diff != "" { + if diff := cmp.Diff(tc.wantJsonfiles, ff.Data); diff != "" { t.Errorf("want != got:\n%v\n", diff) } - if diff := cmp.Diff(tc.wantTxtfiles, ff.fs); diff != "" { + if diff := cmp.Diff(tc.wantTxtfiles, ff.Fs); diff != "" { t.Errorf("want != got:\n%v\n", diff) } } diff --git a/objects/numbersmoother.go b/objects/numbersmoother.go index 07b87d1..60c0162 100644 --- a/objects/numbersmoother.go +++ b/objects/numbersmoother.go @@ -1,14 +1,17 @@ package objects import ( + "ModCreator/types" + "fmt" "math" ) var ( // because arrays can't be constant - posRounded = []string{"posX", "posY", "posZ"} - rotRounded = []string{"rotX", "rotY", "rotZ"} - colorRounded = []string{"b", "g", "r", "a"} + posRounded = []string{"posX", "posY", "posZ"} + rotRounded = []string{"rotX", "rotY", "rotZ"} + colorRounded = []string{"b", "g", "r", "a"} + arbitraryRounded = []string{"x", "y", "z"} ) func roundFloat(val float64, precision uint) float64 { @@ -26,14 +29,14 @@ func Smooth(objraw interface{}) interface{} { for _, key := range posRounded { if val, ok := obj[key]; ok { if fl, ok := val.(float64); ok { - obj[key] = roundFloat(fl, 3) + obj[key] = smoothPos(fl) } } } for _, key := range rotRounded { if val, ok := obj[key]; ok { if fl, ok := val.(float64); ok { - obj[key] = math.Mod(roundFloat(fl, 0)+360, 360) + obj[key] = smoothRot(fl) } } } @@ -46,3 +49,73 @@ func Smooth(objraw interface{}) interface{} { } return obj } + +func smoothArbitrary(objraw interface{}, round func(float64) float64) (types.J, error) { + smoothed := types.J{} + obj, ok := objraw.(map[string]interface{}) + if !ok { + obj, ok = objraw.(types.J) + if !ok { + return nil, fmt.Errorf("Couldn't convert object, was type %T", objraw) + } + } + + for _, key := range arbitraryRounded { + if val, ok := obj[key]; ok { + if fl, ok := val.(float64); ok { + smoothed[key] = round(fl) + } + } + } + if len(smoothed) != len(obj) { + return nil, fmt.Errorf("unexpected keys match in %v", obj) + } + + return smoothed, nil +} + +// SmoothSnapPoints will consume an array of snap points and smooth all of them +func SmoothSnapPoints(rawsps interface{}) ([]map[string]interface{}, error) { + arr, ok := rawsps.([]map[string]interface{}) + if !ok { + return nil, fmt.Errorf("SnapPoints is expected to be an array of objects; was %T", rawsps) + } + smth := []map[string]interface{}{} + for _, sp := range arr { + + smthsp := types.J{} + if pos, ok := sp["Position"]; ok { + v, err := smoothArbitrary(pos, smoothPos) + if err != nil { + return nil, fmt.Errorf("smoothArbitrary(%v, positional): %v", pos, err) + } + smthsp["Position"] = v + } + if rot, ok := sp["Rotation"]; ok { + v, err := smoothArbitrary(rot, smoothRot) + if err != nil { + return nil, fmt.Errorf("smoothArbitrary(%v, rotational): %v", rot, err) + } + smthsp["Rotation"] = v + } + if len(sp) != len(smthsp) { + return nil, fmt.Errorf("Unexpected key found in array of snap points. full obj: %v", sp) + } + + smth = append(smth, smthsp) + } + return smth, nil +} + +// SmoothAngle smooths x,y,z per angle rules +func SmoothAngle(objraw interface{}) (interface{}, error) { + return smoothArbitrary(objraw, smoothRot) +} + +func smoothPos(f float64) float64 { + return roundFloat(f, 3) +} + +func smoothRot(f float64) float64 { + return math.Mod(roundFloat(f, 0)+360, 360) +} diff --git a/objects/numbersmoother_test.go b/objects/numbersmoother_test.go index 772a05c..01c17a0 100644 --- a/objects/numbersmoother_test.go +++ b/objects/numbersmoother_test.go @@ -1,6 +1,7 @@ package objects import ( + "ModCreator/types" "testing" "github.com/google/go-cmp/cmp" @@ -62,7 +63,7 @@ func TestColor(t *testing.T) { "r": 0.42513, "g": 0.333333333, "b": 0.914525304, - "a": 0.5, + "a": float64(0.5), } got := Smooth(j) want := map[string]interface{}{ @@ -94,3 +95,53 @@ func TestColorTransparent(t *testing.T) { t.Errorf("want != got:\n%v\n", diff) } } + +func TestArray(t *testing.T) { + j := []map[string]interface{}{ + {"Position": types.J{ + "x": float64(-1.822), + "y": float64(0.100), + "z": float64(0.616), + }, + "Rotation": types.J{ + "x": float64(0), + "y": float64(0), + "z": float64(0), + }, + }, + { + "Rotation": types.J{ + "x": float64(0), + "y": float64(0), + "z": float64(0), + }, + }, + } + got, err := SmoothSnapPoints(j) + if err != nil { + t.Fatalf("SmoothSnapPoints(): %v", err) + } + want := []map[string]interface{}{ + {"Position": types.J{ + "x": -1.822, + "y": 0.100, + "z": 0.616, + }, + "Rotation": types.J{ + "x": float64(0), + "y": float64(0), + "z": float64(0), + }, + }, + { + "Rotation": types.J{ + "x": float64(0), + "y": float64(0), + "z": float64(0), + }, + }, + } + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("want != got:\n%v\n", diff) + } +} diff --git a/objects/objects.go b/objects/objects.go index 4bb7795..475beb9 100644 --- a/objects/objects.go +++ b/objects/objects.go @@ -66,11 +66,24 @@ func (o *objConfig) parseFromJSON(data map[string]interface{}) error { file.TryParseIntoStr(&o.data, "ContainedObjects_path", &o.subObjDir) file.TryParseIntoStrArray(&o.data, "ContainedObjects_order", &o.subObjOrder) - if trans, ok := o.data["Transform"]; ok { - o.data["Transform"] = Smooth(trans) + for _, needSmoothing := range []string{"Transform", "ColorDiffuse"} { + if v, ok := o.data[needSmoothing]; ok { + o.data[needSmoothing] = Smooth(v) + } + } + if v, ok := o.data["AltLookAngle"]; ok { + vv, err := SmoothAngle(v) + if err != nil { + return fmt.Errorf("SmoothAngle(<%s>): %v", "AltLookAngle", err) + } + o.data["AltLookAngle"] = vv } - if color, ok := o.data["ColorDiffuse"]; ok { - o.data["ColorDiffuse"] = Smooth(color) + if sp, ok := o.data["AttachedSnapPoints"]; ok { + sm, err := SmoothSnapPoints(sp) + if err != nil { + return fmt.Errorf("SmoothSnapPoints(<%s>): %v", o.guid, err) + } + o.data["AttachedSnapPoints"] = sm } if rawObjs, ok := o.data["ContainedObjects"]; ok { diff --git a/objects/objects_test.go b/objects/objects_test.go index fe39df1..19e3aba 100644 --- a/objects/objects_test.go +++ b/objects/objects_test.go @@ -4,130 +4,14 @@ import ( "encoding/json" "fmt" "log" - "path" - "strings" "testing" + "ModCreator/tests" "ModCreator/types" "github.com/google/go-cmp/cmp" ) -type fakeFiles struct { - fs map[string]string - data map[string]types.J -} - -func (f *fakeFiles) EncodeFromFile(s string) (string, error) { - if _, ok := f.fs[s]; !ok { - return "", fmt.Errorf("fake file <%s> not found", s) - } - return f.fs[s], nil -} -func (f *fakeFiles) ReadObj(s string) (map[string]interface{}, error) { - if _, ok := f.data[s]; !ok { - return nil, fmt.Errorf("fake file <%s> not found", s) - } - return f.data[s], nil -} -func (f *fakeFiles) ReadObjArray(s string) ([]map[string]interface{}, error) { - return nil, fmt.Errorf("unimplemented") -} -func (f *fakeFiles) WriteObj(data map[string]interface{}, path string) error { - f.data[path] = data - return nil -} -func (f *fakeFiles) WriteObjArray(data []map[string]interface{}, path string) error { - return fmt.Errorf("unimplemented") -} -func (f *fakeFiles) EncodeToFile(script, file string) error { - f.fs[file] = script - return nil -} -func (f *fakeFiles) CreateDir(a, b string) (string, error) { - // return the "chosen" directory name for next folder - return b, nil -} -func (f *fakeFiles) ListFilesAndFolders(relpath string) ([]string, []string, error) { - // ignore non json files. i don't think they Matter - files := []string{} - folders := []string{} - for k := range f.data { - if strings.HasPrefix(k, relpath) { - left := k - if relpath != "" { - left = strings.Replace(k, relpath+"/", "", 1) - } - if strings.Contains(left, "/") { - // this is a folder not a file - folders = append(folders, path.Join(relpath, strings.Split(left, "/")[0])) - } else { - files = append(files, path.Join(relpath, left)) - } - } - } - return files, folders, nil -} - -func TestTestframework(t *testing.T) { - for _, tc := range []struct { - input map[string]types.J - path string - wantFiles []string - wantFolders []string - }{ - { - input: map[string]types.J{ - "foo.json": types.J{}, - }, - path: "", - wantFiles: []string{"foo.json"}, - wantFolders: []string{}, - }, - { - input: map[string]types.J{ - "foo.json": types.J{}, - "foo/bar.json": types.J{}, - "foo2/bar.json": types.J{}, - }, - path: "", - wantFiles: []string{"foo.json"}, - wantFolders: []string{"foo", "foo2"}, - }, - { - input: map[string]types.J{ - "foo/bar.json": types.J{}, - "foo/bar/baz.json": types.J{}, - }, - path: "foo", - wantFiles: []string{"foo/bar.json"}, - wantFolders: []string{"foo/bar"}, - }, - } { - ff := fakeFiles{ - data: tc.input, - } - gotFiles, gotFolders, err := ff.ListFilesAndFolders(tc.path) - if err != nil { - t.Fatalf("ListFilesAndFolders(%s): %v", tc.path, err) - } - asset := cmp.Transformer("tomap", func(in []string) map[string]bool { - out := map[string]bool{} - for _, i := range in { - out[i] = true - } - return out - }) - if diff := cmp.Diff(tc.wantFiles, gotFiles, asset); diff != "" { - t.Errorf("want != got:\n%v\n", diff) - } - if diff := cmp.Diff(tc.wantFolders, gotFolders, asset); diff != "" { - t.Errorf("want != got:\n%v\n", diff) - } - } - -} - func TestObjPrintToFile(t *testing.T) { for _, tc := range []struct { o *objConfig @@ -147,15 +31,12 @@ func TestObjPrintToFile(t *testing.T) { }, }, } { - ff := &fakeFiles{ - fs: map[string]string{}, - data: map[string]types.J{}, - } + ff := tests.NewFF() err := tc.o.printToFile("path/to/", ff, ff, ff) if err != nil { t.Errorf("printing %v, got %v", tc.o, err) } - if diff := cmp.Diff(tc.want, ff.data["path/to/"+tc.wantFilename]); diff != "" { + if diff := cmp.Diff(tc.want, ff.Data["path/to/"+tc.wantFilename]); diff != "" { t.Errorf("want != got:\n%v\n", diff) } } @@ -212,8 +93,8 @@ func TestObjPrinting(t *testing.T) { }, }, } { - l := &fakeFiles{ - fs: map[string]string{ + l := &tests.FakeFiles{ + Fs: map[string]string{ "core/AgendaDeck.ttslua": "var a = 42;", }, } @@ -364,19 +245,16 @@ func TestObjPrintingToFile(t *testing.T) { }, }, } { - ff := &fakeFiles{ - fs: map[string]string{}, - data: map[string]types.J{}, - } + ff := tests.NewFF() err := tc.o.printToFile(tc.folder, ff, ff, ff) if err != nil { t.Errorf("printing %v, got %v", tc.o, err) } for _, wantFJ := range tc.wantObjs { - got, ok := ff.data[wantFJ.file] + got, ok := ff.Data[wantFJ.file] if !ok { - log.Printf("%v\n", ff.data) + log.Printf("%v\n", ff.Data) t.Fatalf("Wanted file %s, not found", wantFJ.file) } if diff := cmp.Diff(wantFJ.content, got); diff != "" { @@ -386,10 +264,9 @@ func TestObjPrintingToFile(t *testing.T) { // compare lua script state if tc.wantLSS.file != "" { - got, ok := ff.fs[tc.wantLSS.file] + got, ok := ff.Fs[tc.wantLSS.file] if !ok { - log.Printf("fs: %v", ff.fs) - log.Printf("data %v", ff.data) + ff.DebugFileNames(t.Logf) t.Errorf("wanted luascript state %s, didn't find", tc.wantLSS.file) } if diff := cmp.Diff(tc.wantLSS.content, got); diff != "" { @@ -532,10 +409,7 @@ func TestPrintAllObjs(t *testing.T) { }, }, } { - ff := &fakeFiles{ - data: map[string]types.J{}, - fs: map[string]string{}, - } + ff := tests.NewFF() gotOrder, err := PrintObjectStates("", ff, ff, ff, tc.objs) if err != nil { t.Fatalf("error not expected %v", err) @@ -544,7 +418,7 @@ func TestPrintAllObjs(t *testing.T) { t.Errorf("want != got:\n%v\n", diff) } for _, w := range tc.wants { - got, ok := ff.data[w.name] + got, ok := ff.Data[w.name] if !ok { t.Errorf("wanted filename %s not present in data", w.name) } @@ -557,10 +431,7 @@ func TestPrintAllObjs(t *testing.T) { } func TestDBPrint(t *testing.T) { - ff := &fakeFiles{ - fs: map[string]string{}, - data: map[string]types.J{}, - } + ff := tests.NewFF() for _, tc := range []struct { root map[string]*objConfig orderInput []string @@ -608,8 +479,8 @@ func jn(i int) json.Number { func TestParseFromFile(t *testing.T) { - ff := &fakeFiles{ - data: map[string]types.J{}, + ff := &tests.FakeFiles{ + Data: map[string]types.J{}, } for _, tc := range []struct { name string @@ -627,7 +498,7 @@ func TestParseFromFile(t *testing.T) { }, }, } { - ff.data[tc.name] = tc.input + ff.Data[tc.name] = tc.input o := objConfig{} err := o.parseFromFile(tc.name, ff) if err != nil { diff --git a/tests/e2e_test.go b/tests/e2e_test.go index 5e3c3ff..1acca35 100644 --- a/tests/e2e_test.go +++ b/tests/e2e_test.go @@ -3,12 +3,7 @@ package tests import ( "ModCreator/file" "ModCreator/mod" - "ModCreator/types" - "encoding/json" - "fmt" - "path" "path/filepath" - "strings" "testing" "github.com/google/go-cmp/cmp" @@ -21,90 +16,6 @@ var ( expectedObjStates = "ObjectStates" ) -type fakeFiles struct { - fs map[string]string - data map[string]types.J -} - -func newFF() *fakeFiles { - return &fakeFiles{ - fs: map[string]string{}, - data: map[string]types.J{}, - } -} - -func (f *fakeFiles) EncodeFromFile(s string) (string, error) { - if _, ok := f.fs[s]; !ok { - return "", fmt.Errorf("fake file <%s> not found", s) - } - return f.fs[s], nil -} -func (f *fakeFiles) ReadObj(s string) (map[string]interface{}, error) { - if _, ok := f.data[s]; !ok { - return nil, fmt.Errorf("fake file <%s> not found", s) - } - b, err := json.MarshalIndent(f.data[s], "", " ") - if err != nil { - return nil, err - } - var v map[string]interface{} - err = json.Unmarshal(b, &v) - if err != nil { - return nil, err - } - return v, nil -} - -func (f *fakeFiles) ReadObjArray(s string) ([]map[string]interface{}, error) { - return nil, fmt.Errorf("unimplemented") -} -func (f *fakeFiles) WriteObj(data map[string]interface{}, path string) error { - f.data[path] = data - return nil -} -func (f *fakeFiles) WriteObjArray(data []map[string]interface{}, path string) error { - return fmt.Errorf("unimplemented") -} -func (f *fakeFiles) EncodeToFile(script, file string) error { - f.fs[file] = script - return nil -} -func (f *fakeFiles) CreateDir(a, b string) (string, error) { - // return the "chosen" directory name for next folder - return b, nil -} -func (f *fakeFiles) ListFilesAndFolders(relpath string) ([]string, []string, error) { - // ignore non json files. i don't think they Matter - files := []string{} - folders := []string{} - for k := range f.data { - if strings.HasPrefix(k, relpath) { - left := k - if relpath != "" { - left = strings.Replace(k, relpath+"/", "", 1) - } - if strings.Contains(left, "/") { - // this is a folder not a file - folders = append(folders, path.Join(relpath, strings.Split(left, "/")[0])) - } else { - files = append(files, path.Join(relpath, left)) - } - } - } - return files, folders, nil -} - -func (f *fakeFiles) debugFileNames(log func(s string, args ...interface{})) { - log("txt files:\n") - for fn := range f.fs { - log("\t%s\n", fn) - } - log("json files:\n") - for fn := range f.data { - log("\t%s\n", fn) - } -} - func TestAllReverseThenBuild(t *testing.T) { paths, err := filepath.Glob(filepath.Join("testdata", "e2e", "*.json")) if err != nil { @@ -132,9 +43,9 @@ func TestAllReverseThenBuild(t *testing.T) { if err != nil { t.Fatalf("Error parsing %s : %v", path, err) } - modsettings := newFF() - finalOutput := newFF() - objsAndLua := newFF() + modsettings := NewFF() + finalOutput := NewFF() + objsAndLua := NewFF() r := mod.Reverser{ ModSettingsWriter: modsettings, @@ -148,8 +59,8 @@ func TestAllReverseThenBuild(t *testing.T) { t.Fatalf("Error reversing : %v", err) } - objsAndLua.debugFileNames(t.Logf) - finalOutput.debugFileNames(t.Logf) + objsAndLua.DebugFileNames(t.Logf) + finalOutput.DebugFileNames(t.Logf) reversedConfig, _ := finalOutput.ReadObj("config.json") t.Logf("%v\n", reversedConfig) diff --git a/tests/fakefiles.go b/tests/fakefiles.go new file mode 100644 index 0000000..5b6490d --- /dev/null +++ b/tests/fakefiles.go @@ -0,0 +1,125 @@ +package tests + +import ( + "ModCreator/types" + "encoding/json" + "fmt" + "path" + "strings" +) + +// FakeFiles allows for mocking of the file system to use in tests +type FakeFiles struct { + Fs map[string]string + Data map[string]types.J +} + +// NewFF helps you not forget to initialize maps +func NewFF() *FakeFiles { + return &FakeFiles{ + Fs: map[string]string{}, + Data: map[string]types.J{}, + } +} + +// EncodeFromFile satisfies LuaReader +func (f *FakeFiles) EncodeFromFile(s string) (string, error) { + if _, ok := f.Fs[s]; !ok { + return "", fmt.Errorf("fake file <%s> not found", s) + } + return f.Fs[s], nil +} + +// ReadObj satisfies JSONReader +func (f *FakeFiles) ReadObj(s string) (map[string]interface{}, error) { + if _, ok := f.Data[s]; !ok { + return nil, fmt.Errorf("fake file <%s> not found", s) + } + b, err := json.MarshalIndent(f.Data[s], "", " ") + if err != nil { + return nil, err + } + var v map[string]interface{} + err = json.Unmarshal(b, &v) + if err != nil { + return nil, err + } + return v, nil +} + +// ReadObjArray satisfies JSONReader +func (f *FakeFiles) ReadObjArray(s string) ([]map[string]interface{}, error) { + wrapper, ok := f.Data[s] + if !ok { + return nil, fmt.Errorf("fake file <%s> not found", s) + } + raw, ok := wrapper["testarray"] + if !ok { + return nil, fmt.Errorf("fake obj(%s) was not faked to have array in it", s) + } + val, ok := raw.([]map[string]interface{}) + if !ok { + return nil, fmt.Errorf("fake obj array in %s was not array, was %T", s, raw) + } + return val, nil +} + +// WriteObj satisfies JSONWriter +func (f *FakeFiles) WriteObj(data map[string]interface{}, path string) error { + f.Data[path] = data + return nil +} + +// WriteObjArray satisfies JSONWriter +func (f *FakeFiles) WriteObjArray(data []map[string]interface{}, path string) error { + f.Data[path] = types.J{ + "testarray": data, + } + return nil +} + +// EncodeToFile satisfies LuaWriter +func (f *FakeFiles) EncodeToFile(script, file string) error { + f.Fs[file] = script + return nil +} + +// CreateDir satisfies DirCreator +func (f *FakeFiles) CreateDir(a, b string) (string, error) { + // return the "chosen" directory name for next folder + return b, nil +} + +// ListFilesAndFolders satisfies DirExplorer +func (f *FakeFiles) ListFilesAndFolders(relpath string) ([]string, []string, error) { + // ignore non json files. i don't think they Matter + files := []string{} + folders := []string{} + for k := range f.Data { + if strings.HasPrefix(k, relpath) { + left := k + if relpath != "" { + left = strings.Replace(k, relpath+"/", "", 1) + } + if strings.Contains(left, "/") { + // this is a folder not a file + folders = append(folders, path.Join(relpath, strings.Split(left, "/")[0])) + } else { + files = append(files, path.Join(relpath, left)) + } + } + } + return files, folders, nil +} + +// DebugFileNames lets you log the file structure +func (f *FakeFiles) DebugFileNames(log func(s string, args ...interface{})) { + log("txt files:\n") + for fn := range f.Fs { + log("\t%s\n", fn) + } + log("json files:\n") + for fn := range f.Data { + log("\t%s\n", fn) + } +} diff --git a/tests/fakefiles_test.go b/tests/fakefiles_test.go new file mode 100644 index 0000000..8c65950 --- /dev/null +++ b/tests/fakefiles_test.go @@ -0,0 +1,67 @@ +package tests + +import ( + "ModCreator/types" + "testing" + + "github.com/google/go-cmp/cmp" +) + +func TestTestframework(t *testing.T) { + for _, tc := range []struct { + input map[string]types.J + path string + wantFiles []string + wantFolders []string + }{ + { + input: map[string]types.J{ + "foo.json": types.J{}, + }, + path: "", + wantFiles: []string{"foo.json"}, + wantFolders: []string{}, + }, + { + input: map[string]types.J{ + "foo.json": types.J{}, + "foo/bar.json": types.J{}, + "foo2/bar.json": types.J{}, + }, + path: "", + wantFiles: []string{"foo.json"}, + wantFolders: []string{"foo", "foo2"}, + }, + { + input: map[string]types.J{ + "foo/bar.json": types.J{}, + "foo/bar/baz.json": types.J{}, + }, + path: "foo", + wantFiles: []string{"foo/bar.json"}, + wantFolders: []string{"foo/bar"}, + }, + } { + ff := FakeFiles{ + Data: tc.input, + } + gotFiles, gotFolders, err := ff.ListFilesAndFolders(tc.path) + if err != nil { + t.Fatalf("ListFilesAndFolders(%s): %v", tc.path, err) + } + asset := cmp.Transformer("tomap", func(in []string) map[string]bool { + out := map[string]bool{} + for _, i := range in { + out[i] = true + } + return out + }) + if diff := cmp.Diff(tc.wantFiles, gotFiles, asset); diff != "" { + t.Errorf("want != got:\n%v\n", diff) + } + if diff := cmp.Diff(tc.wantFolders, gotFolders, asset); diff != "" { + t.Errorf("want != got:\n%v\n", diff) + } + } + +}