Add metadata generator tool for test spec metadata generation.

Bug: 296873595
Test: Manual test (use go test inside tools/metadata/testdata)

Change-Id: I404b57224828149f26bcf4deadb662f513886231
This commit is contained in:
Aditya Choudhary 2023-11-02 11:18:40 +00:00
parent 2c8ece0b25
commit 51f97c1963
14 changed files with 411 additions and 0 deletions

14
tools/metadata/Android.bp Normal file
View file

@ -0,0 +1,14 @@
package {
default_applicable_licenses: ["Android-Apache-2.0"],
}
blueprint_go_binary {
name: "metadata",
deps: [
"soong-testing-test_spec_proto",
"golang-protobuf-proto",
],
srcs: [
"generator.go",
]
}

169
tools/metadata/generator.go Normal file
View file

@ -0,0 +1,169 @@
package main
import (
"flag"
"fmt"
"io"
"log"
"os"
"sort"
"strings"
"sync"
"android/soong/testing/test_spec_proto"
"google.golang.org/protobuf/proto"
)
type keyToLocksMap struct {
locks sync.Map
}
func (kl *keyToLocksMap) GetLockForKey(key string) *sync.Mutex {
mutex, _ := kl.locks.LoadOrStore(key, &sync.Mutex{})
return mutex.(*sync.Mutex)
}
func getSortedKeys(syncMap *sync.Map) []string {
var allKeys []string
syncMap.Range(
func(key, _ interface{}) bool {
allKeys = append(allKeys, key.(string))
return true
},
)
sort.Strings(allKeys)
return allKeys
}
func writeOutput(
outputFile string,
allMetadata []*test_spec_proto.TestSpec_OwnershipMetadata,
) {
testSpec := &test_spec_proto.TestSpec{
OwnershipMetadataList: allMetadata,
}
data, err := proto.Marshal(testSpec)
if err != nil {
log.Fatal(err)
}
file, err := os.Create(outputFile)
if err != nil {
log.Fatal(err)
}
defer file.Close()
_, err = file.Write(data)
if err != nil {
log.Fatal(err)
}
}
func readFileToString(filePath string) string {
file, err := os.Open(filePath)
if err != nil {
log.Fatal(err)
}
defer file.Close()
data, err := io.ReadAll(file)
if err != nil {
log.Fatal(err)
}
return string(data)
}
func processProtobuf(
filePath string, ownershipMetadataMap *sync.Map, keyLocks *keyToLocksMap,
errCh chan error, wg *sync.WaitGroup,
) {
defer wg.Done()
fileContent := strings.TrimRight(readFileToString(filePath), "\n")
testData := test_spec_proto.TestSpec{}
err := proto.Unmarshal([]byte(fileContent), &testData)
if err != nil {
errCh <- err
return
}
ownershipMetadata := testData.GetOwnershipMetadataList()
for _, metadata := range ownershipMetadata {
key := metadata.GetTargetName()
lock := keyLocks.GetLockForKey(key)
lock.Lock()
value, loaded := ownershipMetadataMap.LoadOrStore(
key, []*test_spec_proto.TestSpec_OwnershipMetadata{metadata},
)
if loaded {
existingMetadata := value.([]*test_spec_proto.TestSpec_OwnershipMetadata)
isDuplicate := false
for _, existing := range existingMetadata {
if metadata.GetTrendyTeamId() != existing.GetTrendyTeamId() {
errCh <- fmt.Errorf(
"Conflicting trendy team IDs found for %s at:\n%s with teamId"+
": %s,\n%s with teamId: %s",
key,
metadata.GetPath(), metadata.GetTrendyTeamId(), existing.GetPath(),
existing.GetTrendyTeamId(),
)
lock.Unlock()
return
}
if metadata.GetTrendyTeamId() == existing.GetTrendyTeamId() && metadata.GetPath() == existing.GetPath() {
isDuplicate = true
break
}
}
if !isDuplicate {
existingMetadata = append(existingMetadata, metadata)
ownershipMetadataMap.Store(key, existingMetadata)
}
}
lock.Unlock()
}
}
func main() {
inputFile := flag.String("inputFile", "", "Input file path")
outputFile := flag.String("outputFile", "", "Output file path")
flag.Parse()
if *inputFile == "" || *outputFile == "" {
fmt.Println("Usage: metadata -inputFile <input file path> -outputFile <output file path>")
os.Exit(1)
}
inputFileData := strings.TrimRight(readFileToString(*inputFile), "\n")
filePaths := strings.Split(inputFileData, "\n")
ownershipMetadataMap := &sync.Map{}
keyLocks := &keyToLocksMap{}
errCh := make(chan error, len(filePaths))
var wg sync.WaitGroup
for _, filePath := range filePaths {
wg.Add(1)
go processProtobuf(filePath, ownershipMetadataMap, keyLocks, errCh, &wg)
}
wg.Wait()
close(errCh)
for err := range errCh {
log.Fatal(err)
}
allKeys := getSortedKeys(ownershipMetadataMap)
var allMetadata []*test_spec_proto.TestSpec_OwnershipMetadata
for _, key := range allKeys {
value, _ := ownershipMetadataMap.Load(key)
metadataList := value.([]*test_spec_proto.TestSpec_OwnershipMetadata)
allMetadata = append(allMetadata, metadataList...)
}
writeOutput(*outputFile, allMetadata)
}

7
tools/metadata/go.mod Normal file
View file

@ -0,0 +1,7 @@
module android/soong/tools/metadata
require google.golang.org/protobuf v0.0.0
replace google.golang.org/protobuf v0.0.0 => ../../../external/golang-protobuf
go 1.18

10
tools/metadata/go.work Normal file
View file

@ -0,0 +1,10 @@
go 1.18
use (
.
../../../../external/golang-protobuf
../../../soong/testing/test_spec_proto
)
replace google.golang.org/protobuf v0.0.0 => ../../../../external/golang-protobuf

View file

@ -0,0 +1,22 @@
.
java-test-module-name-one
Android.bp12345
.
java-test-module-name-six
Android.bp12346
.
java-test-module-name-six
Aqwerty.bp12346
.
java-test-module-name-six
Apoiuyt.bp12346
.
java-test-module-name-two
Android.bp12345
.
java-test-module-name-two
Asdfghj.bp12345
.
java-test-module-name-two
Azxcvbn.bp12345

13
tools/metadata/testdata/file1.txt vendored Normal file
View file

@ -0,0 +1,13 @@
.
java-test-module-name-one
Android.bp12345
.
java-test-module-name-two
Android.bp12345
.
java-test-module-name-two
Asdfghj.bp12345
.
java-test-module-name-two
Azxcvbn.bp12345

25
tools/metadata/testdata/file2.txt vendored Normal file
View file

@ -0,0 +1,25 @@
.
java-test-module-name-one
Android.bp12345
.
java-test-module-name-six
Android.bp12346
.
java-test-module-name-one
Android.bp12345
.
java-test-module-name-six
Aqwerty.bp12346
.
java-test-module-name-six
Apoiuyt.bp12346
.
java-test-module-name-six
Apoiuyt.bp12346
.
java-test-module-name-six
Apoiuyt.bp12346
.
java-test-module-name-six
Apoiuyt.bp12346

13
tools/metadata/testdata/file3.txt vendored Normal file
View file

@ -0,0 +1,13 @@
.
java-test-module-name-one
Android.bp12345
.
java-test-module-name-two
Android.bp12345
.
java-test-module-name-two
Asdfghj.bp12345
.
java-test-module-name-two
Azxcvbn.bp12345

25
tools/metadata/testdata/file4.txt vendored Normal file
View file

@ -0,0 +1,25 @@
.
java-test-module-name-one
Android.bp12345
.
java-test-module-name-six
Android.bp12346
.
java-test-module-name-one
Android.bp12346
.
java-test-module-name-six
Aqwerty.bp12346
.
java-test-module-name-six
Apoiuyt.bp12346
.
java-test-module-name-six
Apoiuyt.bp12346
.
java-test-module-name-six
Apoiuyt.bp12346
.
java-test-module-name-six
Apoiuyt.bp12346

View file

@ -0,0 +1,22 @@
.
java-test-module-name-one
Android.bp12345
.
java-test-module-name-six
Android.bp12346
.
java-test-module-name-six
Aqwerty.bp12346
.
java-test-module-name-six
Apoiuyt.bp12346
.
java-test-module-name-two
Android.bp12345
.
java-test-module-name-two
Asdfghj.bp12345
.
java-test-module-name-two
Azxcvbn.bp12345

View file

@ -0,0 +1,2 @@
file1.txt
file2.txt

View file

@ -0,0 +1,2 @@
file3.txt
file4.txt

View file

@ -0,0 +1,65 @@
package main
import (
"fmt"
"io/ioutil"
"os/exec"
"strings"
"testing"
)
func TestMetadata(t *testing.T) {
cmd := exec.Command(
"metadata", "-inputFile", "./inputFiles.txt", "-outputFile",
"./generatedOutputFile.txt",
)
stderr, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("Error running metadata command: %s. Error: %v", stderr, err)
}
// Read the contents of the expected output file
expectedOutput, err := ioutil.ReadFile("./expectedOutputFile.txt")
if err != nil {
t.Fatalf("Error reading expected output file: %s", err)
}
// Read the contents of the generated output file
generatedOutput, err := ioutil.ReadFile("./generatedOutputFile.txt")
if err != nil {
t.Fatalf("Error reading generated output file: %s", err)
}
fmt.Println()
// Compare the contents
if string(expectedOutput) != string(generatedOutput) {
t.Errorf("Generated file contents do not match the expected output")
}
}
func TestMetadataNegativeCase(t *testing.T) {
cmd := exec.Command(
"metadata", "-inputFile", "./inputFilesNegativeCase.txt", "-outputFile",
"./generatedOutputFileNegativeCase.txt",
)
stderr, err := cmd.CombinedOutput()
if err == nil {
t.Fatalf(
"Expected an error, but the metadata command executed successfully. Output: %s",
stderr,
)
}
expectedError := "Conflicting trendy team IDs found for java-test-module" +
"-name-one at:\nAndroid.bp with teamId: 12346," +
"\nAndroid.bp with teamId: 12345"
if !strings.Contains(
strings.TrimSpace(string(stderr)), strings.TrimSpace(expectedError),
) {
t.Errorf(
"Unexpected error message. Expected to contain: %s, Got: %s",
expectedError, stderr,
)
}
}

22
tools/metadata/testdata/outputFile.txt vendored Normal file
View file

@ -0,0 +1,22 @@
.
java-test-module-name-one
Android.bp12345
.
java-test-module-name-six
Android.bp12346
.
java-test-module-name-six
Aqwerty.bp12346
.
java-test-module-name-six
Apoiuyt.bp12346
.
java-test-module-name-two
Android.bp12345
.
java-test-module-name-two
Asdfghj.bp12345
.
java-test-module-name-two
Azxcvbn.bp12345