Skip to content

Commit 382adf8

Browse files
committed
feat(component): Add fingerprint tag to all component configurations
1 parent aa085ed commit 382adf8

File tree

4 files changed

+106
-10
lines changed

4 files changed

+106
-10
lines changed

internal/projectconfig/build.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ type CheckConfig struct {
1313
// Skip indicates whether the %check section should be disabled for this component.
1414
Skip bool `toml:"skip,omitempty" json:"skip,omitempty" jsonschema:"title=Skip check,description=Disables the %check section by prepending 'exit 0' when set to true"`
1515
// SkipReason provides a required justification when Skip is true.
16-
SkipReason string `toml:"skip_reason,omitempty" json:"skipReason,omitempty" jsonschema:"title=Skip reason,description=Required justification for skipping the %check section"`
16+
SkipReason string `toml:"skip_reason,omitempty" json:"skipReason,omitempty" jsonschema:"title=Skip reason,description=Required justification for skipping the %check section" fingerprint:"-"`
1717
}
1818

1919
// Validate checks that required fields are set when Skip is true.
@@ -43,27 +43,27 @@ type ComponentBuildConfig struct {
4343
// Check section configuration.
4444
Check CheckConfig `toml:"check,omitempty" json:"check,omitempty" jsonschema:"title=Check configuration,description=Configuration for the %check section"`
4545
// Failure configuration and policy for this component's build.
46-
Failure ComponentBuildFailureConfig `toml:"failure,omitempty" json:"failure,omitempty" jsonschema:"title=Build failure configuration,description=Configuration and policy regarding build failures for this component."`
46+
Failure ComponentBuildFailureConfig `toml:"failure,omitempty" json:"failure,omitempty" jsonschema:"title=Build failure configuration,description=Configuration and policy regarding build failures for this component." fingerprint:"-"`
4747
// Hints for how or when to build the component; must not be required for correctness of builds.
48-
Hints ComponentBuildHints `toml:"hints,omitempty" json:"hints,omitempty" jsonschema:"title=Build hints,description=Non-essential hints for how or when to build the component."`
48+
Hints ComponentBuildHints `toml:"hints,omitempty" json:"hints,omitempty" jsonschema:"title=Build hints,description=Non-essential hints for how or when to build the component." fingerprint:"-"`
4949
}
5050

5151
// ComponentBuildFailureConfig encapsulates configuration and policy regarding a component's
5252
// build failure.
5353
type ComponentBuildFailureConfig struct {
5454
// Expected indicates that this component is expected to fail building. This is intended to be used as a temporary
5555
// marker for components that are expected to fail until they can be fixed.
56-
Expected bool `toml:"expected,omitempty" json:"expected,omitempty" jsonschema:"title=Expected failure,description=Indicates that this component is expected to fail building."`
56+
Expected bool `toml:"expected,omitempty" json:"expected,omitempty" jsonschema:"title=Expected failure,description=Indicates that this component is expected to fail building." fingerprint:"-"`
5757
// ExpectedReason provides a required justification when Expected is true.
58-
ExpectedReason string `toml:"expected-reason,omitempty" json:"expectedReason,omitempty" jsonschema:"title=Expected failure reason,description=Required justification for why this component is expected to fail building."`
58+
ExpectedReason string `toml:"expected-reason,omitempty" json:"expectedReason,omitempty" jsonschema:"title=Expected failure reason,description=Required justification for why this component is expected to fail building." fingerprint:"-"`
5959
}
6060

6161
// ComponentBuildHints encapsulates non-essential hints for how or when to build a component.
6262
// These are not required for correctness of builds, but may be used by tools to provide guidance
6363
// or optimizations.
6464
type ComponentBuildHints struct {
6565
// Expensive indicates that building this component is relatively expensive compared to the rest of the distro.
66-
Expensive bool `toml:"expensive,omitempty" json:"expensive,omitempty" jsonschema:"title=Expensive to build,description=Indicates that building this component is expensive and should be carefully considered when scheduling."`
66+
Expensive bool `toml:"expensive,omitempty" json:"expensive,omitempty" jsonschema:"title=Expensive to build,description=Indicates that building this component is expensive and should be carefully considered when scheduling." fingerprint:"-"`
6767
}
6868

6969
// Validate checks that the build configuration is valid.

internal/projectconfig/component.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ type Origin struct {
4949
// SourceFileReference encapsulates a reference to a specific source file artifact.
5050
type SourceFileReference struct {
5151
// Reference to the component to which the source file belongs.
52-
Component ComponentReference `toml:"-" json:"-"`
52+
Component ComponentReference `toml:"-" json:"-" fingerprint:"-"`
5353

5454
// Name of the source file; must be non-empty.
5555
Filename string `toml:"filename" json:"filename"`
@@ -111,11 +111,11 @@ func (g ComponentGroupConfig) WithAbsolutePaths(referenceDir string) ComponentGr
111111
// Defines a component.
112112
type ComponentConfig struct {
113113
// The component's name; not actually present in serialized files.
114-
Name string `toml:"-" json:"name" table:",sortkey"`
114+
Name string `toml:"-" json:"name" table:",sortkey" fingerprint:"-"`
115115

116116
// Reference to the source config file that this definition came from; not present
117117
// in serialized files.
118-
SourceConfigFile *ConfigFile `toml:"-" json:"-" table:"-"`
118+
SourceConfigFile *ConfigFile `toml:"-" json:"-" table:"-" fingerprint:"-"`
119119

120120
// Where to get its spec and adjacent files from.
121121
Spec SpecSource `toml:"spec,omitempty" json:"spec,omitempty" jsonschema:"title=Spec,description=Identifies where to find the spec for this component"`
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
package projectconfig_test
5+
6+
import (
7+
"reflect"
8+
"testing"
9+
10+
"github.com/microsoft/azure-linux-dev-tools/internal/projectconfig"
11+
"github.com/stretchr/testify/assert"
12+
)
13+
14+
// TestAllFingerprintedFieldsHaveDecision verifies that every field in every
15+
// fingerprinted struct has been consciously categorized as either included
16+
// (no fingerprint tag) or excluded (`fingerprint:"-"`).
17+
//
18+
// This test serves two purposes:
19+
// 1. It ensures that newly added fields default to **included** in the fingerprint
20+
// (the safe default — you get a false positive, never a false negative).
21+
// 2. It catches accidental removal of `fingerprint:"-"` tags from excluded fields,
22+
// since all exclusions are tracked in expectedExclusions.
23+
func TestAllFingerprintedFieldsHaveDecision(t *testing.T) {
24+
// All struct types whose fields participate in component fingerprinting.
25+
// When adding a new struct that feeds into the fingerprint, add it here.
26+
fingerprintedStructs := []reflect.Type{
27+
reflect.TypeFor[projectconfig.ComponentConfig](),
28+
reflect.TypeFor[projectconfig.ComponentBuildConfig](),
29+
reflect.TypeFor[projectconfig.CheckConfig](),
30+
reflect.TypeFor[projectconfig.ComponentBuildFailureConfig](),
31+
reflect.TypeFor[projectconfig.ComponentBuildHints](),
32+
reflect.TypeFor[projectconfig.ComponentOverlay](),
33+
reflect.TypeFor[projectconfig.SpecSource](),
34+
reflect.TypeFor[projectconfig.DistroReference](),
35+
reflect.TypeFor[projectconfig.SourceFileReference](),
36+
reflect.TypeFor[projectconfig.Origin](),
37+
}
38+
39+
// Maps "StructName.FieldName" for every field that should carry a
40+
// `fingerprint:"-"` tag. Catches accidental tag removal.
41+
expectedExclusions := map[string]bool{
42+
// ComponentConfig
43+
"ComponentConfig.Name": true,
44+
"ComponentConfig.SourceConfigFile": true,
45+
46+
// ComponentBuildConfig
47+
"ComponentBuildConfig.Failure": true,
48+
"ComponentBuildConfig.Hints": true,
49+
50+
// CheckConfig
51+
"CheckConfig.SkipReason": true,
52+
53+
// ComponentBuildFailureConfig — entire struct excluded via parent, but individual
54+
// fields are also tagged so reflection on the struct alone is consistent.
55+
"ComponentBuildFailureConfig.Expected": true,
56+
"ComponentBuildFailureConfig.ExpectedReason": true,
57+
58+
// ComponentBuildHints — entire struct excluded via parent, fields also tagged.
59+
"ComponentBuildHints.Expensive": true,
60+
61+
// ComponentOverlay
62+
"ComponentOverlay.Description": true,
63+
64+
// SourceFileReference
65+
"SourceFileReference.Component": true,
66+
}
67+
68+
// Collect all actual exclusions found via reflection.
69+
actualExclusions := make(map[string]bool)
70+
71+
for _, st := range fingerprintedStructs {
72+
for i := range st.NumField() {
73+
field := st.Field(i)
74+
key := st.Name() + "." + field.Name
75+
76+
tag := field.Tag.Get("fingerprint")
77+
if tag == "-" {
78+
actualExclusions[key] = true
79+
}
80+
}
81+
}
82+
83+
// Verify every expected exclusion is actually present.
84+
for key := range expectedExclusions {
85+
assert.Truef(t, actualExclusions[key],
86+
"expected field %q to have `fingerprint:\"-\"` tag, but it does not — "+
87+
"was the tag accidentally removed?", key)
88+
}
89+
90+
// Verify no unexpected exclusions exist.
91+
for key := range actualExclusions {
92+
assert.Truef(t, expectedExclusions[key],
93+
"field %q has `fingerprint:\"-\"` tag but is not in expectedExclusions — "+
94+
"add it to expectedExclusions if the exclusion is intentional", key)
95+
}
96+
}

internal/projectconfig/overlay.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ type ComponentOverlay struct {
1717
// The type of overlay to apply.
1818
Type ComponentOverlayType `toml:"type" json:"type" validate:"required" jsonschema:"enum=spec-add-tag,enum=spec-insert-tag,enum=spec-set-tag,enum=spec-update-tag,enum=spec-remove-tag,enum=spec-prepend-lines,enum=spec-append-lines,enum=spec-search-replace,enum=patch-add,enum=patch-remove,enum=file-prepend-lines,enum=file-search-replace,enum=file-add,enum=file-remove,enum=file-rename,title=Overlay type,description=The type of overlay to apply"`
1919
// Human readable description of overlay; primarily present to document the need for the change.
20-
Description string `toml:"description,omitempty" json:"description,omitempty" jsonschema:"title=Description,description=Human readable description of overlay"`
20+
Description string `toml:"description,omitempty" json:"description,omitempty" jsonschema:"title=Description,description=Human readable description of overlay" fingerprint:"-"`
2121

2222
// For overlays that apply to non-spec files, indicates the filename. For overlays that can
2323
// apply to multiple files, supports glob patterns (including globstar).

0 commit comments

Comments
 (0)