From 1d3284f1a99dbfdfb8fac6c4f7ebc102dc9e68b3 Mon Sep 17 00:00:00 2001 From: theresa Date: Thu, 21 Apr 2022 15:27:59 +0200 Subject: [PATCH] Tests: Add unit tests for geo search --- .../search/photos_geo_filter_near_test.go | 469 +++++++++++++++++ internal/search/photos_geo_filter_s2_test.go | 473 ++++++++++++++++++ 2 files changed, 942 insertions(+) create mode 100644 internal/search/photos_geo_filter_near_test.go create mode 100644 internal/search/photos_geo_filter_s2_test.go diff --git a/internal/search/photos_geo_filter_near_test.go b/internal/search/photos_geo_filter_near_test.go new file mode 100644 index 000000000..e437d68b3 --- /dev/null +++ b/internal/search/photos_geo_filter_near_test.go @@ -0,0 +1,469 @@ +package search + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/photoprism/photoprism/internal/form" +) + +func TestPhotosGeoFilterNear(t *testing.T) { + t.Run("pt9jtdre2lvl0y24", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Near = "pt9jtdre2lvl0y24" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 8) + }) + t.Run("pr2xu7myk7wrbk30", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Near = "pr2xu7myk7wrbk30" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 26) + }) + t.Run("StartsWithPercent", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Near = "%gold" + _, err := PhotosGeo(f) + + assert.Equal(t, err.Error(), "record not found") + }) + t.Run("CenterPercent", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Near = "I love % dog" + _, err := PhotosGeo(f) + + assert.Equal(t, err.Error(), "record not found") + }) + //TODO error + /*t.Run("EndsWithPercent", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Near = "sale%" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("StartsWithAmpersand", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Near = "&IlikeFood" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("CenterAmpersand", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Near = "Pets & Dogs" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("EndsWithAmpersand", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Near = "Light&" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("StartsWithSingleQuote", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Near = "'Family" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("CenterSingleQuote", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Near = "Father's type" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, len(photos), 0) + }) + t.Run("EndsWithSingleQuote", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Near = "Ice Cream'" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("StartsWithAsterisk", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Near = "*Forrest" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("CenterAsterisk", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Near = "My*Kids" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("EndsWithAsterisk", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Near = "Yoga***" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("StartsWithPipe", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Near = "|Banana" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("CenterPipe", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Near = "Red|Green" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, len(photos), 0) + }) + t.Run("EndsWithPipe", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Near = "Blue|" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, len(photos), 0) + }) + t.Run("StartsWithNumber", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Near = "345 Shirt" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("CenterNumber", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Near = "type555 Blue" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("EndsWithNumber", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Near = "Route 66" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + })*/ +} + +func TestPhotosGeoQueryNear(t *testing.T) { + t.Run("pt9jtdre2lvl0y24", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Query = "near:pt9jtdre2lvl0y24" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 8) + }) + t.Run("pr2xu7myk7wrbk30", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Query = "near:pr2xu7myk7wrbk30" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 26) + }) + //TODO error + /*t.Run("pt9jtdre2lvl0y24 pipe pr2xu7myk7wrbk30", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Query = "near:pt9jtdre2lvl0y24|pr2xu7myk7wrbk30" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("StartsWithPercent", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Query = "near:\"%gold\"" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("CenterPercent", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Query = "near:\"I love % dog\"" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("EndsWithPercent", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Query = "near:\"sale%\"" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("StartsWithAmpersand", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Query = "near:\"&IlikeFood\"" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("CenterAmpersand", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Query = "near:\"Pets & Dogs\"" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("EndsWithAmpersand", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Query = "near:\"Light&\"" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("StartsWithSingleQuote", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Query = "near:\"'Family\"" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("CenterSingleQuote", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Query = "near:\"Father's type\"" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, len(photos), 0) + }) + t.Run("EndsWithSingleQuote", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Query = "near:\"Ice Cream'\"" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("StartsWithAsterisk", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Query = "near:\"*Forrest\"" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("CenterAsterisk", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Query = "near:\"My*Kids\"" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("EndsWithAsterisk", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Query = "near:\"Yoga***\"" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("StartsWithPipe", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Query = "near:\"|Banana\"" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("CenterPipe", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Query = "near:\"Red|Green\"" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, len(photos), 0) + }) + t.Run("EndsWithPipe", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Query = "near:\"Blue|\"" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("StartsWithNumber", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Query = "near:\"345 Shirt\"" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("CenterNumber", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Query = "near:\"type555 Blue\"" + + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("EndsWithNumber", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Query = "near:\"Route 66\"" + + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + })*/ +} diff --git a/internal/search/photos_geo_filter_s2_test.go b/internal/search/photos_geo_filter_s2_test.go new file mode 100644 index 000000000..11636b046 --- /dev/null +++ b/internal/search/photos_geo_filter_s2_test.go @@ -0,0 +1,473 @@ +package search + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/photoprism/photoprism/internal/form" +) + +func TestPhotosGeoFilterS2(t *testing.T) { + t.Run("1ef744d1e283", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.S2 = "1ef744d1e283" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 3) + }) + t.Run("85d1ea7d382c", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.S2 = "85d1ea7d382c" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 8) + }) + t.Run("StartsWithPercent", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.S2 = "%gold" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("CenterPercent", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.S2 = "I love % dog" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("EndsWithPercent", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.S2 = "sale%" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("StartsWithAmpersand", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.S2 = "&IlikeFood" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("CenterAmpersand", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.S2 = "Pets & Dogs" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("EndsWithAmpersand", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.S2 = "Light&" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("StartsWithSingleQuote", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.S2 = "'Family" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("CenterSingleQuote", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.S2 = "Father's type" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, len(photos), 0) + }) + t.Run("EndsWithSingleQuote", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.S2 = "Ice Cream'" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("StartsWithAsterisk", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.S2 = "*Forrest" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("CenterAsterisk", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.S2 = "My*Kids" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("EndsWithAsterisk", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.S2 = "Yoga***" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("StartsWithPipe", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.S2 = "|Banana" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("CenterPipe", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.S2 = "Red|Green" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, len(photos), 0) + }) + t.Run("EndsWithPipe", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.S2 = "Blue|" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, len(photos), 0) + }) + t.Run("StartsWithNumber", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.S2 = "345 Shirt" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("CenterNumber", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.S2 = "type555 Blue" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("EndsWithNumber", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.S2 = "Route 66" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) +} + +func TestPhotosGeoQueryS2(t *testing.T) { + t.Run("s2:1ef744d1e283", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Query = "s2:1ef744d1e283" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 3) + }) + t.Run("s2:85d1ea7d382c", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Query = "s2:85d1ea7d382c" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 8) + }) + t.Run("85d1ea7d382c pipe 1ef744d1e283", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Query = "s2:85d1ea7d382c|1ef744d1e283" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("StartsWithPercent", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Query = "s2:\"%gold\"" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("CenterPercent", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Query = "s2:\"I love % dog\"" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("EndsWithPercent", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Query = "s2:\"sale%\"" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("StartsWithAmpersand", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Query = "s2:\"&IlikeFood\"" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("CenterAmpersand", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Query = "s2:\"Pets & Dogs\"" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("EndsWithAmpersand", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Query = "s2:\"Light&\"" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("StartsWithSingleQuote", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Query = "s2:\"'Family\"" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("CenterSingleQuote", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Query = "s2:\"Father's type\"" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, len(photos), 0) + }) + t.Run("EndsWithSingleQuote", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Query = "s2:\"Ice Cream'\"" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("StartsWithAsterisk", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Query = "s2:\"*Forrest\"" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("CenterAsterisk", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Query = "s2:\"My*Kids\"" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("EndsWithAsterisk", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Query = "s2:\"Yoga***\"" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("StartsWithPipe", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Query = "s2:\"|Banana\"" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("CenterPipe", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Query = "s2:\"Red|Green\"" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, len(photos), 0) + }) + t.Run("EndsWithPipe", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Query = "s2:\"Blue|\"" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("StartsWithNumber", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Query = "s2:\"345 Shirt\"" + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("CenterNumber", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Query = "s2:\"type555 Blue\"" + + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) + t.Run("EndsWithNumber", func(t *testing.T) { + var f form.SearchPhotosGeo + + f.Query = "s2:\"Route 66\"" + + photos, err := PhotosGeo(f) + + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(photos), 0) + }) +}