@@ -35,10 +35,13 @@ import (
35
35
"testing"
36
36
"time"
37
37
38
+ "github.com/google/go-cmp/cmp"
39
+ "github.com/google/go-cmp/cmp/cmpopts"
38
40
coreapi "k8s.io/api/core/v1"
39
41
"k8s.io/apimachinery/pkg/api/resource"
40
42
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
41
43
"k8s.io/apimachinery/pkg/util/sets"
44
+ yaml "sigs.k8s.io/yaml/goyaml.v3"
42
45
43
46
prowapi "sigs.k8s.io/prow/pkg/apis/prowjobs/v1"
44
47
cfg "sigs.k8s.io/prow/pkg/config"
@@ -1393,3 +1396,180 @@ func TestKubernetesProwJobsShouldNotUseDeprecatedScenarios(t *testing.T) {
1393
1396
t .Logf ("summary: %v/%v jobs using deprecated scenarios" , jobsToFix , len (jobs ))
1394
1397
}
1395
1398
}
1399
+
1400
+ func TestKubernetesPresubmitJobs (t * testing.T ) {
1401
+ jobs := c .AllStaticPresubmits ([]string {"kubernetes/kubernetes" })
1402
+ var expected presubmitJobs
1403
+
1404
+ for _ , job := range jobs {
1405
+ if ! job .AlwaysRun && job .RunIfChanged == "" {
1406
+ // Manually triggered, no additional review needed.
1407
+ continue
1408
+ }
1409
+
1410
+ // Mirror those attributes of the job which must trigger additional reviews
1411
+ // or are needed to identify the job.
1412
+ j := presubmitJob {
1413
+ Name : job .Name ,
1414
+ SkipBranches : job .SkipBranches ,
1415
+ Branches : job .Branches ,
1416
+
1417
+ Optional : job .Optional ,
1418
+ RunIfChanged : job .RunIfChanged ,
1419
+ SkipIfOnlyChanged : job .SkipIfOnlyChanged ,
1420
+ }
1421
+
1422
+ // This uses separate top-level fields instead of job attributes to
1423
+ // make it more obvious when run_if_changed is used.
1424
+ if job .AlwaysRun {
1425
+ expected .AlwaysRun = append (expected .AlwaysRun , j )
1426
+ } else {
1427
+ expected .RunIfChanged = append (expected .RunIfChanged , j )
1428
+ }
1429
+
1430
+ }
1431
+ expected .Normalize ()
1432
+
1433
+ // Encode the expected content.
1434
+ var expectedData bytes.Buffer
1435
+ if _ , err := expectedData .Write ([]byte (`# AUTOGENERATED by "UPDATE_FIXTURE_DATA=true go test ./config/tests/jobs". DO NOT EDIT!
1436
+
1437
+ ` )); err != nil {
1438
+ t .Fatalf ("unexpected error writing into buffer: %v" , err )
1439
+ }
1440
+
1441
+ encoder := yaml .NewEncoder (& expectedData )
1442
+ encoder .SetIndent (4 )
1443
+ if err := encoder .Encode (expected ); err != nil {
1444
+ t .Fatalf ("unexpected error encoding %s: %v" , presubmitsFile , err )
1445
+ }
1446
+
1447
+ // Compare. This proceeds on read or decoding errors because
1448
+ // the file might get re-generated below.
1449
+ var actual presubmitJobs
1450
+ actualData , err := os .ReadFile (presubmitsFile )
1451
+ if err != nil && ! os .IsNotExist (err ) {
1452
+ t .Errorf ("unexpected error: %v" , err )
1453
+ }
1454
+ if err := yaml .Unmarshal (actualData , & actual ); err != nil {
1455
+ t .Errorf ("unexpected error decoding %s: %v" , presubmitsFile , err )
1456
+ }
1457
+
1458
+ // First check the in-memory structs. The diff is nicer for them (more context).
1459
+ diff := cmp .Diff (actual , expected )
1460
+ if diff == "" {
1461
+ // Next check the encoded data. This should only be different on test updates.
1462
+ diff = cmp .Diff (string (actualData ), expectedData .String (), cmpopts .AcyclicTransformer ("SplitLines" , func (s string ) []string {
1463
+ return strings .Split (s , "\n " )
1464
+ }))
1465
+ }
1466
+
1467
+ if diff != "" {
1468
+ t .Errorf (`
1469
+ %s is out-dated. Detected differences (- actual, + expected):
1470
+ %s
1471
+
1472
+ Blocking pre-submit jobs must be for stable, important features.
1473
+ Non-blocking pre-submit jobs should only be run automatically if they meet
1474
+ the criteria outlined in https://github.com/kubernetes/community/pull/8196.
1475
+
1476
+ To ensure that this is considered when defining pre-submit jobs, they
1477
+ need to be listed in %s. If the pre-submit job is really needed,
1478
+ re-run the test with UPDATE_FIXTURE_DATA=true and include the modified
1479
+ file.
1480
+
1481
+
1482
+ ` , presubmitsFile , diff , presubmitsFile )
1483
+ if value , _ := os .LookupEnv ("UPDATE_FIXTURE_DATA" ); value == "true" {
1484
+ if err := os .WriteFile (presubmitsFile , expectedData .Bytes (), 0644 ); err != nil {
1485
+ t .Fatalf ("unexpected error: %v" , err )
1486
+ }
1487
+ }
1488
+ }
1489
+ }
1490
+
1491
+ // presubmitsFile contains the following struct.
1492
+ const presubmitsFile = "presubmit-jobs.yaml"
1493
+
1494
+ type presubmitJobs struct {
1495
+ AlwaysRun []presubmitJob `yaml:"always_run"`
1496
+ RunIfChanged []presubmitJob `yaml:"run_if_changed"`
1497
+ }
1498
+ type presubmitJob struct {
1499
+ Name string `yaml:"name"`
1500
+ SkipBranches []string `yaml:"skip_branches,omitempty"`
1501
+ Branches []string `yaml:"branches,omitempty"`
1502
+ Optional bool `yaml:"optional,omitempty"`
1503
+ RunIfChanged string `yaml:"run_if_changed,omitempty"`
1504
+ SkipIfOnlyChanged string `yaml:"skip_if_only_changed,omitempty"`
1505
+ }
1506
+
1507
+ func (p * presubmitJobs ) Normalize () {
1508
+ sortJobs (& p .AlwaysRun )
1509
+ sortJobs (& p .RunIfChanged )
1510
+ }
1511
+
1512
+ func sortJobs (jobs * []presubmitJob ) {
1513
+ for _ , job := range * jobs {
1514
+ sort .Strings (job .SkipBranches )
1515
+ sort .Strings (job .Branches )
1516
+ }
1517
+ sort .Slice (* jobs , func (i , j int ) bool {
1518
+ switch strings .Compare ((* jobs )[i ].Name , (* jobs )[j ].Name ) {
1519
+ case - 1 :
1520
+ return true
1521
+ case 1 :
1522
+ return false
1523
+ }
1524
+ switch slices .Compare ((* jobs )[i ].SkipBranches , (* jobs )[j ].SkipBranches ) {
1525
+ case - 1 :
1526
+ return true
1527
+ case 1 :
1528
+ return false
1529
+ }
1530
+ switch slices .Compare ((* jobs )[i ].Branches , (* jobs )[j ].Branches ) {
1531
+ case - 1 :
1532
+ return true
1533
+ case 1 :
1534
+ return false
1535
+ }
1536
+ return false
1537
+ })
1538
+
1539
+ // If a job has the same settings regardless of the branch, then
1540
+ // we can reduce to a single entry without the branch info.
1541
+ shorterJobs := make ([]presubmitJob , 0 , len (* jobs ))
1542
+ for i := 0 ; i < len (* jobs ); {
1543
+ job := (* jobs )[i ]
1544
+ job .Branches = nil
1545
+ job .SkipBranches = nil
1546
+
1547
+ if sameSettings (* jobs , job ) {
1548
+ shorterJobs = append (shorterJobs , job )
1549
+ // Fast-forward to next job.
1550
+ for i < len (* jobs ) && (* jobs )[i ].Name == job .Name {
1551
+ i ++
1552
+ }
1553
+ } else {
1554
+ // Keep all of the different entries.
1555
+ for i < len (* jobs ) && (* jobs )[i ].Name == job .Name {
1556
+ shorterJobs = append (shorterJobs , (* jobs )[i ])
1557
+ }
1558
+ }
1559
+ }
1560
+ * jobs = shorterJobs
1561
+ }
1562
+
1563
+ func sameSettings (jobs []presubmitJob , ref presubmitJob ) bool {
1564
+ for _ , job := range jobs {
1565
+ if job .Name != ref .Name {
1566
+ continue
1567
+ }
1568
+ if job .Optional != ref .Optional ||
1569
+ job .RunIfChanged != ref .RunIfChanged ||
1570
+ job .SkipIfOnlyChanged != ref .SkipIfOnlyChanged {
1571
+ return false
1572
+ }
1573
+ }
1574
+ return true
1575
+ }
0 commit comments