@@ -35,10 +35,13 @@ import (
3535 "testing"
3636 "time"
3737
38+ "github.com/google/go-cmp/cmp"
39+ "github.com/google/go-cmp/cmp/cmpopts"
3840 coreapi "k8s.io/api/core/v1"
3941 "k8s.io/apimachinery/pkg/api/resource"
4042 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
4143 "k8s.io/apimachinery/pkg/util/sets"
44+ yaml "sigs.k8s.io/yaml/goyaml.v3"
4245
4346 prowapi "sigs.k8s.io/prow/pkg/apis/prowjobs/v1"
4447 cfg "sigs.k8s.io/prow/pkg/config"
@@ -1393,3 +1396,180 @@ func TestKubernetesProwJobsShouldNotUseDeprecatedScenarios(t *testing.T) {
13931396 t .Logf ("summary: %v/%v jobs using deprecated scenarios" , jobsToFix , len (jobs ))
13941397 }
13951398}
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