Skip to content

Commit 4e4e80e

Browse files
Added test suite to test against all dev projects
Signed-off-by: Łukasz Gryglicki <[email protected]>
1 parent b36f5a8 commit 4e4e80e

File tree

4 files changed

+155
-24
lines changed

4 files changed

+155
-24
lines changed

cla-backend-go/cmd/response_metrics.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package cmd
55

66
import (
7+
"sync"
78
"time"
89

910
"github.com/linuxfoundation/easycla/cla-backend-go/utils"
@@ -18,32 +19,33 @@ type responseMetrics struct {
1819
expire time.Time
1920
}
2021

21-
var reqMap = make(map[string]*responseMetrics, 5)
22+
var reqMap sync.Map
2223

2324
// requestStart holds the request ID, method and timing information in a small structure
2425
func requestStart(reqID, method string) {
2526
now, _ := utils.CurrentTime()
26-
reqMap[reqID] = &responseMetrics{
27+
rm := &responseMetrics{
2728
reqID: reqID,
2829
method: method,
2930
start: now,
3031
elapsed: 0,
3132
expire: now.Add(time.Minute * 5),
3233
}
34+
reqMap.Store(reqID, rm)
3335
}
3436

3537
// getRequestMetrics returns the response metrics based on the request id value
3638
func getRequestMetrics(reqID string) *responseMetrics {
37-
if x, found := reqMap[reqID]; found {
39+
if val, found := reqMap.Load(reqID); found {
40+
rm := val.(*responseMetrics)
3841
now, _ := utils.CurrentTime()
39-
x.elapsed = now.Sub(x.start)
40-
return x
42+
rm.elapsed = now.Sub(rm.start)
43+
return rm
4144
}
42-
4345
return nil
4446
}
4547

4648
// clearRequestMetrics removes the request from the map
4749
func clearRequestMetrics(reqID string) {
48-
delete(reqMap, reqID)
50+
reqMap.Delete(reqID)
4951
}

tests/py2go/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,6 @@
1515
- `` export XACL="$(cat ../../x-acl.secret)" ``.
1616
- `` make ``.
1717
- `` DEBUG=1 PROJECT_UUID=88ee12de-122b-4c46-9046-19422054ed8d PY_API_URL=https://api.lfcla.dev.platform.linuxfoundation.org GO_API_URL=https://api-gw.dev.platform.linuxfoundation.org/cla-service make ``.
18+
- `` MAX_PARALLEL=8 PY_API_URL=https://api.lfcla.dev.platform.linuxfoundation.org go test -v -run '^TestAllProjectsCompatAPI$' ``.
1819
- To run a specific test case(s): `` DEBUG=1 PROJECT_UUID=88ee12de-122b-4c46-9046-19422054ed8d PY_API_URL=https://api.lfcla.dev.platform.linuxfoundation.org go test -v -run '^TestProjectCompatAPI$' ``.
1920
- Manually via `cURL`: `` curl -s -XGET http://127.0.0.1:5001/v4/project-compat/01af041c-fa69-4052-a23c-fb8c1d3bef24 | jq . ``.

tests/py2go/api_test.go

Lines changed: 82 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ import (
77
"net/http"
88
"os"
99
"sort"
10+
"strconv"
11+
"strings"
12+
"sync"
1013
"testing"
1114
"time"
1215

@@ -20,6 +23,7 @@ var (
2023
PY_API_URL string
2124
GO_API_URL string
2225
DEBUG bool
26+
MAX_PARALLEL int
2327
PROJECT_UUID string
2428
ProjectAPIPath = [3]string{"/v2/project/%s", "/v4/project/%s", "/v4/project-compat/%s"}
2529
ProjectAPIKeyMapping = map[string]string{
@@ -88,6 +92,16 @@ func init() {
8892
if dbg != "" {
8993
DEBUG = true
9094
}
95+
MAX_PARALLEL = 1
96+
par := os.Getenv("MAX_PARALLEL")
97+
if par != "" {
98+
iPar, err := strconv.Atoi(par)
99+
if err != nil {
100+
fmt.Printf("MAX_PARALLEL environment value should be integer >= 1\n")
101+
} else if iPar > 0 {
102+
MAX_PARALLEL = iPar
103+
}
104+
}
91105
PROJECT_UUID = os.Getenv("PROJECT_UUID")
92106
}
93107

@@ -253,23 +267,7 @@ func compareNestedFields(t *testing.T, pyData, goData, keyMapping map[string]int
253267
}
254268
}
255269

256-
func TestProjectCompatAPI(t *testing.T) {
257-
projectId := PROJECT_UUID
258-
if projectId == "" {
259-
projectId = uuid.New().String()
260-
putTestItem("projects", "project_id", projectId, "S", map[string]interface{}{
261-
"project_name": "CNCF",
262-
"project_icla_enabled": true,
263-
"project_ccla_enabled": true,
264-
"project_ccla_requires_icla_signature": true,
265-
"date_created": "2022-11-21T10:31:31Z",
266-
"date_modified": "2023-02-23T13:14:48Z",
267-
"foundation_sfid": "a09410000182dD2AAI",
268-
"version": "2",
269-
}, DEBUG)
270-
defer deleteTestItem("projects", "project_id", projectId, "S", DEBUG)
271-
}
272-
270+
func runProjectCompatAPIForProject(t *testing.T, projectId string) {
273271
apiURL := PY_API_URL + fmt.Sprintf(ProjectAPIPath[0], projectId)
274272
Debugf("Py API call: %s\n", apiURL)
275273
oldResp, err := http.Get(apiURL)
@@ -324,6 +322,73 @@ func TestProjectCompatAPI(t *testing.T) {
324322
}
325323
}
326324

325+
func TestProjectCompatAPI(t *testing.T) {
326+
projectId := PROJECT_UUID
327+
if projectId == "" {
328+
projectId = uuid.New().String()
329+
putTestItem("projects", "project_id", projectId, "S", map[string]interface{}{
330+
"project_name": "CNCF",
331+
"project_icla_enabled": true,
332+
"project_ccla_enabled": true,
333+
"project_ccla_requires_icla_signature": true,
334+
"date_created": "2022-11-21T10:31:31Z",
335+
"date_modified": "2023-02-23T13:14:48Z",
336+
"foundation_sfid": "a09410000182dD2AAI",
337+
"version": "2",
338+
}, DEBUG)
339+
defer deleteTestItem("projects", "project_id", projectId, "S", DEBUG)
340+
}
341+
342+
runProjectCompatAPIForProject(t, projectId)
343+
}
344+
345+
func TestAllProjectsCompatAPI(t *testing.T) {
346+
allProjects := getAllPrimaryKeys("projects", "project_id", "S")
347+
348+
var failedProjects []string
349+
var mtx sync.Mutex
350+
sem := make(chan struct{}, MAX_PARALLEL)
351+
var wg sync.WaitGroup
352+
353+
for _, projectID := range allProjects {
354+
projID, ok := projectID.(string)
355+
if !ok {
356+
t.Errorf("Expected string project_id, got: %T", projectID)
357+
continue
358+
}
359+
360+
wg.Add(1)
361+
sem <- struct{}{}
362+
363+
go func(projID string) {
364+
defer wg.Done()
365+
defer func() { <-sem }()
366+
367+
// Use t.Run in a thread-safe wrapper with a dummy parent test
368+
t.Run(fmt.Sprintf("ProjectId=%s", projID), func(t *testing.T) {
369+
runProjectCompatAPIForProject(t, projID)
370+
if t.Failed() {
371+
mtx.Lock()
372+
failedProjects = append(failedProjects, projID)
373+
mtx.Unlock()
374+
}
375+
})
376+
}(projID)
377+
}
378+
379+
wg.Wait()
380+
381+
if len(failedProjects) > 0 {
382+
fmt.Fprintf(os.Stderr, "\nFailed Project IDs (%d):\n%s\n\n",
383+
len(failedProjects),
384+
strings.Join(failedProjects, "\n"),
385+
)
386+
t.Fail() // Mark test as failed
387+
} else {
388+
fmt.Println("\nAll projects passed.")
389+
}
390+
}
391+
327392
func TestProjectAPI(t *testing.T) {
328393
if TOKEN == "" || XACL == "" {
329394
t.Fatalf("TOKEN and XACL environment variables must be set")

tests/py2go/dynamo.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,3 +123,66 @@ func deleteTestItem(tableName, keyName string, keyValue interface{}, keyType str
123123
}
124124
Debugf("deleted entry in %s: %s=%s\n", tName, keyName, keyValue)
125125
}
126+
127+
func getAllPrimaryKeys(tableName, keyName, keyType string) []interface{} {
128+
cfg, err := config.LoadDefaultConfig(
129+
context.TODO(),
130+
config.WithRegion(REGION),
131+
config.WithSharedConfigProfile(PROFILE),
132+
)
133+
if err != nil {
134+
log.Fatalf("unable to load SDK config, %v", err)
135+
}
136+
client := dynamodb.NewFromConfig(cfg)
137+
138+
tName := "cla-" + STAGE + "-" + tableName
139+
Debugf("getting all keys form %s\n", tName)
140+
var results []interface{}
141+
var lastEvaluatedKey map[string]types.AttributeValue
142+
143+
for {
144+
input := &dynamodb.ScanInput{
145+
TableName: aws.String(tName),
146+
ProjectionExpression: aws.String(keyName),
147+
ExclusiveStartKey: lastEvaluatedKey,
148+
}
149+
150+
output, err := client.Scan(context.TODO(), input)
151+
if err != nil {
152+
log.Fatalf("Scan error on table %s: %v", tName, err)
153+
}
154+
155+
for _, item := range output.Items {
156+
attr, ok := item[keyName]
157+
if !ok {
158+
Debugf("Key %s not found in item: %+v", keyName, item)
159+
continue
160+
}
161+
162+
switch keyType {
163+
case "S":
164+
if v, ok := attr.(*types.AttributeValueMemberS); ok {
165+
results = append(results, v.Value)
166+
}
167+
case "N":
168+
if v, ok := attr.(*types.AttributeValueMemberN); ok {
169+
results = append(results, v.Value)
170+
}
171+
case "BOOL":
172+
if v, ok := attr.(*types.AttributeValueMemberBOOL); ok {
173+
results = append(results, v.Value)
174+
}
175+
default:
176+
log.Fatalf("Unsupported key type: %s", keyType)
177+
}
178+
}
179+
180+
if output.LastEvaluatedKey == nil || len(output.LastEvaluatedKey) == 0 {
181+
break
182+
}
183+
lastEvaluatedKey = output.LastEvaluatedKey
184+
}
185+
186+
Debugf("got keys: %+v\n", results)
187+
return results
188+
}

0 commit comments

Comments
 (0)