// Copyright 2018 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package bucketize import ( "bytes" "context" "encoding/xml" "fmt" "io" "io/ioutil" "os" "path" "reflect" "strings" "testing" "src/common/golang/shard" "src/common/golang/walk" "src/tools/ak/res/res" ) func TestNormalizeResPaths(t *testing.T) { // Create a temporary directory to house the fake workspace. tmp, err := ioutil.TempDir("", "") if err != nil { t.Fatalf("Can't make temp directory: %v", err) } defer os.RemoveAll(tmp) var resPaths []string fp1 := path.Join(tmp, "foo") _, err = os.Create(fp1) if err != nil { t.Fatalf("Got error while trying to create %s: %v", fp1, err) } resPaths = append(resPaths, fp1) dp1 := path.Join(tmp, "bar", "baz", "qux") if err != os.MkdirAll(dp1, 0777) { t.Fatalf("Got error while trying to create %s: %v", dp1, err) } resPaths = append(resPaths, dp1) // Create a file nested in the directory that is passed in as a resPath. This file will get // injected between fp1 and fp3 because the directory is defined in the middle. Hence, // files added to the directory will appear between fp1 and fp3. This behavior is intended. fInDP1 := path.Join(dp1, "quux") _, err = os.Create(fInDP1) if err != nil { t.Fatalf("Got error while trying to create %s: %v", fInDP1, err) } fp3 := path.Join(tmp, "bar", "corge") _, err = os.Create(fp3) if err != nil { t.Fatalf("Got error while trying to create %s: %v", fp3, err) } resPaths = append(resPaths, fp3) gotFiles, err := walk.Files(resPaths) if err != nil { t.Fatalf("Got error getting the resource paths: %v", err) } gotFileIdxs := make(map[string]int) for i, gotFile := range gotFiles { gotFileIdxs[gotFile] = i } wantFiles := []string{fp1, fInDP1, fp3} if !reflect.DeepEqual(gotFiles, wantFiles) { t.Errorf("DeepEqual(\n%#v\n,\n%#v\n): returned false", gotFiles, wantFiles) } wantFileIdxs := map[string]int{fp1: 0, fInDP1: 1, fp3: 2} if !reflect.DeepEqual(gotFileIdxs, wantFileIdxs) { t.Errorf("DeepEqual(\n%#v\n,\n%#v\n): returned false", gotFileIdxs, wantFileIdxs) } } func TestArchiverWithPartitionSession(t *testing.T) { order := make(map[string]int) ps, err := makePartitionSession(map[res.Type][]io.Writer{}, shard.FNV, order) if err != nil { t.Fatalf("MakePartitionSesion got err: %v", err) } if _, err := makeArchiver([]string{}, ps); err != nil { t.Errorf("MakeArchiver got err: %v", err) } } func TestArchiveNoValues(t *testing.T) { ctx, cxlFn := context.WithCancel(context.Background()) defer cxlFn() a, err := makeArchiver([]string{}, &mockPartitioner{}) if err != nil { t.Fatalf("MakeArchiver got error: %v", err) } a.Archive(ctx) } func TestInternalArchive(t *testing.T) { tcs := []struct { name string p Partitioner pis []*res.PathInfo vrs []*res.ValuesResource ras []ResourcesAttribute errs []error wantErr bool }{ { name: "MultipleResPathInfosAndValuesResources", p: &mockPartitioner{}, pis: []*res.PathInfo{{Path: "foo"}}, vrs: []*res.ValuesResource{ {Src: &res.PathInfo{Path: "bar"}}, {Src: &res.PathInfo{Path: "baz"}}, }, errs: []error{}, }, { name: "NoValues", p: &mockPartitioner{}, pis: []*res.PathInfo{}, vrs: []*res.ValuesResource{}, errs: []error{}, }, { name: "ErrorOccurred", p: &mockPartitioner{}, pis: []*res.PathInfo{{Path: "foo"}}, vrs: []*res.ValuesResource{}, errs: []error{fmt.Errorf("failure")}, wantErr: true, }, } for _, tc := range tcs { t.Run(tc.name, func(t *testing.T) { piC := make(chan *res.PathInfo) go func() { defer close(piC) for _, pi := range tc.pis { piC <- pi } }() vrC := make(chan *res.ValuesResource) go func() { defer close(vrC) for _, vr := range tc.vrs { vrC <- vr } }() raC := make(chan *ResourcesAttribute) go func() { defer close(raC) for _, ra := range tc.ras { nra := new(ResourcesAttribute) *nra = ra raC <- nra } }() errC := make(chan error) go func() { defer close(errC) for _, err := range tc.errs { errC <- err } }() a, err := makeArchiver([]string{}, tc.p) if err != nil { t.Errorf("MakeArchiver got error: %v", err) return } ctx, cxlFn := context.WithCancel(context.Background()) defer cxlFn() if err := a.archive(ctx, piC, vrC, raC, errC); err != nil { if !tc.wantErr { t.Errorf("archive got unexpected error: %v", err) } return } }) } } func TestSyncParseReader(t *testing.T) { tcs := []struct { name string pi *res.PathInfo content *bytes.Buffer want map[string]string wantErr bool }{ { name: "SingleResourcesBlock", pi: &res.PathInfo{}, content: bytes.NewBufferString(` hello world bar `), want: map[string]string{ "introduction-string": "hello world", "foo-string": "bar", "baz-attr": "", }, }, { name: "MultipleResourcesBlocks", pi: &res.PathInfo{}, content: bytes.NewBufferString(` hello world bar 23 `), want: map[string]string{ "introduction-string": "hello world", "foo-string": "bar", }, }, { name: "NamespacedResourcesBlock", pi: &res.PathInfo{}, content: bytes.NewBufferString(` hello world `), want: map[string]string{ "resource_attribute-xmlns:foo": "bar", "namespaced-string": "hello world", }, }, { name: "DeclareStyleable", pi: &res.PathInfo{}, content: bytes.NewBufferString("baz"), want: map[string]string{ "foo-styleable": "baz", "bar-attr": "baz", }, }, { name: "NamespacedStyleableBlock", pi: &res.PathInfo{}, content: bytes.NewBufferString("baz"), want: map[string]string{ "resource_attribute-xmlns:zoo": "zoo", "foo-styleable": "baz", "bar-attr": "baz", }, }, { name: "PluralsStringArrayOutputToStringToo", pi: &res.PathInfo{}, content: bytes.NewBufferString(` barbaz quxquux `), want: map[string]string{ "foo-array": "barbaz", "corge-plurals": "quxquux", }, }, { name: "AttrWithFlagOrEnumChildren", pi: &res.PathInfo{}, content: bytes.NewBufferString(` `), want: map[string]string{ "foo-attr": "", "qux-attr": "", }, }, { name: "Style", pi: &res.PathInfo{}, content: bytes.NewBufferString(` `), want: map[string]string{ "foo-style": "", }, }, { name: "ArraysGoToStingAndInteger", pi: &res.PathInfo{}, content: bytes.NewBufferString(` bar1 `), want: map[string]string{ "foo-array": "bar1", }, }, { name: "NoContent", pi: &res.PathInfo{}, content: &bytes.Buffer{}, want: map[string]string{}, }, { name: "EmptyResources", pi: &res.PathInfo{}, content: bytes.NewBufferString(""), want: map[string]string{}, }, { name: "IgnoredContent", pi: &res.PathInfo{}, content: bytes.NewBufferString(` ignore random string. barqux ignore this random string too. `), want: map[string]string{ "foo-attr": "barqux", }, }, { name: "TagMissingNameAttribute", pi: &res.PathInfo{}, content: bytes.NewBufferString(`MissingNameAttr`), wantErr: true, }, { name: "ItemTagMissingTypeAttribute", pi: &res.PathInfo{}, content: bytes.NewBufferString(`bar`), wantErr: true, }, { name: "ItemTagUnknownTypeAttribute", pi: &res.PathInfo{}, content: bytes.NewBufferString(``), wantErr: true, }, { name: "UnhandledTag", pi: &res.PathInfo{}, content: bytes.NewBufferString(``), wantErr: true, }, { name: "MalFormedXml_OpenResourcesTag", pi: &res.PathInfo{}, content: bytes.NewBufferString(``), wantErr: true, }, { name: "MalFormedXml_Unabalanced", pi: &res.PathInfo{}, content: bytes.NewBufferString(``), wantErr: true, }, { name: "NamespaceUsedWithoutNamespaceDefinition", pi: &res.PathInfo{}, content: bytes.NewBufferString(`Oh no!`), wantErr: true, }, { // Verify parent Encoder is properly shadowing the xml file. name: "NamespaceUsedOutsideOfDefinition", pi: &res.PathInfo{}, content: bytes.NewBufferString(` qux Oh no! `), wantErr: true, }, } for _, tc := range tcs { t.Run(tc.name, func(t *testing.T) { ctx, cxlFn := context.WithCancel(context.Background()) defer cxlFn() vrC := make(chan *res.ValuesResource) raC := make(chan *ResourcesAttribute) errC := make(chan error) go func() { defer close(vrC) defer close(raC) defer close(errC) syncParseReader(ctx, tc.pi, xml.NewDecoder(tc.content), vrC, raC, errC) }() got := make(map[string]string) errMs := make([]string, 0) for errC != nil || vrC != nil { select { case e, ok := <-errC: if !ok { errC = nil } if e != nil { errMs = append(errMs, e.Error()) } case ra, ok := <-raC: if !ok { raC = nil } if ra != nil { a := ra.Attribute got[fmt.Sprintf("resource_attribute-%s:%s", a.Name.Space, a.Name.Local)] = a.Value } case vr, ok := <-vrC: if !ok { vrC = nil } if vr != nil { got[fmt.Sprintf("%s-%s", vr.N.Name, vr.N.Type.String())] = string(vr.Payload) } } } // error handling if tc.wantErr { if len(errMs) == 0 { t.Errorf("syncParseReader expected an error.") } return } if len(errMs) > 0 { t.Errorf("syncParserReader got unexpected error(s): \n%s", strings.Join(errMs, "\n")) return } if !reflect.DeepEqual(got, tc.want) { t.Errorf("DeepEqual(\n%#v\n,\n%#v\n): returned false", got, tc.want) } }) } } // mockPartitioner is a Partitioner mock used for testing. type mockPartitioner struct { strPI []res.PathInfo cvVR []res.ValuesResource ra []*ResourcesAttribute } func (mp *mockPartitioner) Close() error { return nil } func (mp *mockPartitioner) CollectPathResource(src res.PathInfo) { mp.strPI = append(mp.strPI, src) } func (mp *mockPartitioner) CollectValues(vr *res.ValuesResource) error { mp.cvVR = append(mp.cvVR, res.ValuesResource{vr.Src, vr.N, vr.Payload}) return nil } func (mp *mockPartitioner) CollectResourcesAttribute(ra *ResourcesAttribute) { mp.ra = append(mp.ra, ra) }