diff --git a/test/go.mod b/test/go.mod index e1eea2e556..5ae677c6ab 100644 --- a/test/go.mod +++ b/test/go.mod @@ -1,6 +1,6 @@ module github.com/Microsoft/hcsshim/test -go 1.16 +go 1.18 require ( github.com/Microsoft/go-winio v0.4.17 @@ -14,12 +14,47 @@ require ( github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39 github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.8.1 + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c - golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e + golang.org/x/sys v0.12.0 google.golang.org/grpc v1.40.0 k8s.io/cri-api v0.20.6 ) +require ( + github.com/bits-and-blooms/bitset v1.2.0 // indirect + github.com/blang/semver v3.5.1+incompatible // indirect + github.com/cenkalti/backoff/v4 v4.1.1 // indirect + github.com/containerd/cgroups v1.0.1 // indirect + github.com/containerd/console v1.0.2 // indirect + github.com/containerd/continuity v0.1.0 // indirect + github.com/containerd/fifo v1.0.0 // indirect + github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect + github.com/gogo/googleapis v1.4.0 // indirect + github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect + github.com/golang/protobuf v1.5.0 // indirect + github.com/google/go-cmp v0.5.8 // indirect + github.com/google/uuid v1.2.0 // indirect + github.com/hashicorp/errwrap v1.0.0 // indirect + github.com/hashicorp/go-multierror v1.0.0 // indirect + github.com/klauspost/compress v1.11.13 // indirect + github.com/moby/locker v1.0.1 // indirect + github.com/moby/sys/mountinfo v0.4.1 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.0.1 // indirect + github.com/opencontainers/runc v1.0.2 // indirect + github.com/opencontainers/selinux v1.8.2 // indirect + github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f // indirect + go.opencensus.io v0.22.3 // indirect + golang.org/x/net v0.0.0-20210825183410-e898025ed96a // indirect + golang.org/x/text v0.3.6 // indirect + google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a // indirect + google.golang.org/protobuf v1.27.1 // indirect +) + replace ( github.com/Microsoft/hcsshim => ../ google.golang.org/genproto => google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63 diff --git a/test/go.sum b/test/go.sum index 87dde4a102..e88515d286 100644 --- a/test/go.sum +++ b/test/go.sum @@ -319,8 +319,9 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-containerregistry v0.5.1/go.mod h1:Ct15B4yir3PLOP5jsy0GNeYVaIZs/MK/Jz5any1wFW0= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -640,6 +641,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -776,8 +779,9 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -838,7 +842,6 @@ golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= diff --git a/test/pkg/definitions/cmd/cmd.go b/test/pkg/definitions/cmd/cmd.go new file mode 100644 index 0000000000..9272d2d9ad --- /dev/null +++ b/test/pkg/definitions/cmd/cmd.go @@ -0,0 +1,9 @@ +//go:build windows + +package cmd + +import ( + internalcmd "github.com/Microsoft/hcsshim/internal/cmd" +) + +var CreatePipeAndListen = internalcmd.CreatePipeAndListen diff --git a/test/pkg/definitions/cpugroup/cpugroup.go b/test/pkg/definitions/cpugroup/cpugroup.go new file mode 100644 index 0000000000..5bbddb0f51 --- /dev/null +++ b/test/pkg/definitions/cpugroup/cpugroup.go @@ -0,0 +1,13 @@ +//go:build windows + +package cpugroup + +import ( + internalcpugroup "github.com/Microsoft/hcsshim/internal/cpugroup" +) + +var ( + ErrHVStatusInvalidCPUGroupState = internalcpugroup.ErrHVStatusInvalidCPUGroupState + Delete = internalcpugroup.Delete + Create = internalcpugroup.Create +) diff --git a/test/pkg/definitions/lcow/lcow.go b/test/pkg/definitions/lcow/lcow.go new file mode 100644 index 0000000000..667050f82a --- /dev/null +++ b/test/pkg/definitions/lcow/lcow.go @@ -0,0 +1,9 @@ +//go:build windows + +package lcow + +import ( + internallcow "github.com/Microsoft/hcsshim/internal/lcow" +) + +var CreateScratch = internallcow.CreateScratch diff --git a/test/pkg/definitions/processorinfo/processorinfo.go b/test/pkg/definitions/processorinfo/processorinfo.go new file mode 100644 index 0000000000..ad3532d351 --- /dev/null +++ b/test/pkg/definitions/processorinfo/processorinfo.go @@ -0,0 +1,9 @@ +//go:build windows + +package processorinfo + +import ( + internalpinfo "github.com/Microsoft/hcsshim/internal/processorinfo" +) + +var HostProcessorInfo = internalpinfo.HostProcessorInfo diff --git a/test/pkg/definitions/shimdiag/shimdiag.go b/test/pkg/definitions/shimdiag/shimdiag.go new file mode 100644 index 0000000000..f26293254f --- /dev/null +++ b/test/pkg/definitions/shimdiag/shimdiag.go @@ -0,0 +1,16 @@ +//go:build windows + +package shimdiag + +import ( + internalshimdiag "github.com/Microsoft/hcsshim/internal/shimdiag" +) + +var ( + GetShim = internalshimdiag.GetShim + NewShimDiagClient = internalshimdiag.NewShimDiagClient +) + +type ExecProcessRequest = internalshimdiag.ExecProcessRequest +type ShareRequest = internalshimdiag.ShareRequest +type ShimDiagService = internalshimdiag.ShimDiagService diff --git a/test/pkg/doc.go b/test/pkg/doc.go new file mode 100644 index 0000000000..8c27230ec7 --- /dev/null +++ b/test/pkg/doc.go @@ -0,0 +1,6 @@ +package pkg + +// This package is to facilitate testing and has no guarantees about stability. +// Definitions may change without warning. +// +// DO NOT use in production code. diff --git a/test/pkg/flag/flag.go b/test/pkg/flag/flag.go new file mode 100644 index 0000000000..c5f81cae20 --- /dev/null +++ b/test/pkg/flag/flag.go @@ -0,0 +1,194 @@ +package flag + +import ( + "flag" + "strings" + + "github.com/sirupsen/logrus" + "golang.org/x/exp/maps" + "golang.org/x/exp/slices" +) + +const ( + FeatureFlagName = "feature" + ExcludeFeatureFlagName = "exclude" +) + +// NewFeatureFlag defines two flags, [FeatureFlagName] and [ExcludeFeatureFlagName], to +// allow setting and excluding certain features. +func NewFeatureFlag(features []string) *IncludeExcludeStringSet { + fs := NewStringSet(FeatureFlagName, + "`features` to test; can be set multiple times, with a comma-separated list, or both. "+ + "Leave empty to enable all features. "+ + "(supported features: "+strings.Join(features, ", ")+")", false) + + return NewIncludeExcludeStringSet(fs, ExcludeFeatureFlagName, + "`features` to exclude from tests (see "+FeatureFlagName+" for more details)", + features) +} + +// IncludeExcludeStringSet allows unsetting strings seen in a [StringSet]. +type IncludeExcludeStringSet struct { + // flags explicitly included + inc *StringSet + // flags explicitly excluded + exc *StringSet + // def value, if no values set + // we don't error if an unknown value is provided + def []string +} + +// NewIncludeExcludeStringSet returns a new NewIncludeExcludeStringSet. +func NewIncludeExcludeStringSet(include *StringSet, name, usage string, all []string) *IncludeExcludeStringSet { + es := &IncludeExcludeStringSet{ + inc: include, + exc: &StringSet{ + s: make(map[string]struct{}), + cs: include.cs, + }, + def: slices.Clone(all), + } + flag.Var(es, name, usage) + return es +} + +var _ flag.Value = &IncludeExcludeStringSet{} + +func (es *IncludeExcludeStringSet) Set(s string) error { return es.exc.Set(s) } + +func (es *IncludeExcludeStringSet) String() string { + if es == nil { // may be called by flag package on nil receiver + return "" + } + ss := es.strings() + if len(ss) == 0 { + return "" + } + return "[" + strings.Join(ss, ", ") + "]" +} + +func (es *IncludeExcludeStringSet) Strings() []string { return es.strings() } +func (es *IncludeExcludeStringSet) Len() int { return len(es.strings()) } + +func (es *IncludeExcludeStringSet) strings() []string { + ss := es.def + set := make([]string, 0, len(ss)) + if es.inc != nil && es.inc.Len() > 0 { + // include values were explicitly set + ss = es.inc.Strings() + } + for _, s := range ss { + if !es.exc.IsSet(s) { + set = append(set, s) + } + } + return set +} + +func (es *IncludeExcludeStringSet) IsSet(s string) bool { + if es.inc == nil || es.inc.Len() == 0 || es.inc.IsSet(s) { + // either no values were included, or value was explicitly provided + return !es.exc.IsSet(s) + } + return false +} + +// StringSet is a type to be used with the standard library's flag.Var +// function as a custom flag value, similar to "github.com/urfave/cli".StringSet, +// but it only tracks unique instances. +// +// It takes either a comma-separated list of strings, or repeated invocations. +type StringSet struct { + s stringSet + // cs indicates if the set is case sensitive or not + cs bool +} + +var _ flag.Value = &StringSet{} + +// NewStringSet returns a new StringSetFlag with an empty set. +func NewStringSet(name, usage string, caseSensitive bool) *StringSet { + ss := &StringSet{ + s: make(map[string]struct{}), + cs: caseSensitive, + } + flag.Var(ss, name, usage) + return ss +} + +// Strings returns a string slice of the flags provided to the flag +func (ss *StringSet) Strings() []string { return maps.Keys(ss.s) } + +func (ss *StringSet) String() string { + if ss == nil || ss.Len() == 0 { // may be called by flag package on nil receiver + return "" + } + return "[" + strings.Join(ss.Strings(), ", ") + "]" +} + +func (ss *StringSet) Len() int { return len(ss.s) } + +func (ss *StringSet) IsSet(s string) bool { return ss.s.isSet(ss.standardize(s)) } + +// Set is called by `flag` each time the flag is seen when parsing the command line. +func (ss *StringSet) Set(s string) error { + for _, f := range strings.Split(s, ",") { + ss.s.set(ss.standardize(f)) + } + return nil +} + +// standardize formats the feature flag s to be consistent (ie, trim and to lowercase) +func (ss *StringSet) standardize(s string) string { + s = strings.TrimSpace(s) + if !ss.cs { + s = strings.ToLower(s) + } + return s +} + +// stringSet is a set of strings. +type stringSet map[string]struct{} + +func (ss stringSet) set(s string) { ss[s] = struct{}{} } +func (ss stringSet) isSet(s string) bool { + _, ok := ss[s] + return ok +} + +// LogrusLevel is a flag that accepts logrus logging levels as strings. +type LogrusLevel struct { + Level logrus.Level +} + +var _ flag.Value = &LogrusLevel{} + +func NewLogrusLevel(name, value, usage string) *LogrusLevel { + l := &LogrusLevel{} + if lvl, err := logrus.ParseLevel(value); err == nil { + l.Level = lvl + } else { + l.Level = logrus.StandardLogger().Level + } + flag.Var(l, name, usage) + return l +} + +func (l *LogrusLevel) String() string { + // may be called ona nil receiver + // return default level + if l == nil { + return logrus.StandardLogger().Level.String() + } + + return l.Level.String() +} + +func (l *LogrusLevel) Set(s string) error { + lvl, err := logrus.ParseLevel(s) + if err != nil { + return err + } + l.Level = lvl + return nil +} diff --git a/test/pkg/flag/flag_test.go b/test/pkg/flag/flag_test.go new file mode 100644 index 0000000000..04b4f5ef58 --- /dev/null +++ b/test/pkg/flag/flag_test.go @@ -0,0 +1,126 @@ +package flag + +import ( + "fmt" + "testing" + + "golang.org/x/exp/slices" +) + +// tests for testing fixtures ... + +// calling New(IncludeExclude)?StringSet will add it to the default flag set, +// which may cause problems since [testing] already defines flags + +func Test_ExcludeStringSetFlag(t *testing.T) { + all := []string{"one", "two", "three", "four", "zero"} + es := &IncludeExcludeStringSet{ + inc: &StringSet{ + s: make(map[string]struct{}), + cs: false, + }, + exc: &StringSet{ + s: make(map[string]struct{}), + cs: false, + }, + def: slices.Clone(all), + } + + orderlessEq(t, all, es.Strings()) + for _, s := range all { + assert(t, es.IsSet(s), s+" is expected to be set, but is not") + } + + for i, tc := range []struct { + set []string + unset []string + exp []string + }{ + { + unset: []string{"one", "five"}, + exp: []string{"two", "three", "four", "zero"}, + }, + { + unset: []string{"one, three"}, + exp: []string{"two", "four", "zero"}, + }, + { + set: []string{"two,one"}, + exp: []string{"two"}, + }, + { + set: []string{"three,one", " zero "}, + exp: []string{"two", "zero"}, + }, + } { + t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { + for _, s := range tc.set { + must(t, es.inc.Set(s)) + } + for _, s := range tc.unset { + must(t, es.exc.Set(s)) + } + + orderlessEq(t, tc.exp, es.Strings()) + + for _, s := range all { + if slices.Contains(tc.exp, s) { + assert(t, es.IsSet(s), s+" is expected to be set, but is not") + } else { + assert(t, !es.IsSet(s), s+" is not expected to be set, but is") + } + } + }) + } +} + +func Test_StringSetFlag(t *testing.T) { + ss := &StringSet{ + s: make(map[string]struct{}), + cs: false, + } + + must(t, ss.Set("hi,bye,HI")) + must(t, ss.Set("Bye")) + must(t, ss.Set("not a word")) + exp := []string{"hi", "bye", "not a word"} + + orderlessEq(t, exp, ss.Strings()) + for _, s := range exp { + assert(t, ss.IsSet(s), s+"is expected to be set, but is not") + } + for _, s := range []string{"HI", "bYe", "BYE", " not A wOrD"} { + assert(t, ss.IsSet(s), s+"is expected to be set, but is not") + } + for _, s := range []string{"hello", "goodbye", "also not a word"} { + assert(t, !ss.IsSet(s), s+"is not expected to be set, but is") + } +} + +func orderlessEq(tb testing.TB, exp, got []string) { + tb.Helper() + if len(exp) != len(got) { + tb.Fatalf("expected length %d (%s), got %d (%s)", len(exp), exp, len(got), got) + } + ss := stringSet{} + for _, s := range exp { + ss.set(s) + } + for _, s := range got { + assert(tb, ss.isSet(s), "unexpected value: "+s) + } +} + +func assert(tb testing.TB, b bool, msg any) { + tb.Helper() + if !b { + tb.Fatal("assertion failed", msg) + } +} + +func must(tb testing.TB, err error) { + tb.Helper() + if err != nil { + tb.Fatal(err) + } +} diff --git a/test/pkg/manifest/manifest.go b/test/pkg/manifest/manifest.go new file mode 100644 index 0000000000..386de8fa57 --- /dev/null +++ b/test/pkg/manifest/manifest.go @@ -0,0 +1,6 @@ +//go:build windows + +// This package allows tests can include the .syso to manifest them to pick up the right Windows build +package manifest + +//go:generate go run github.com/josephspurrier/goversioninfo/cmd/goversioninfo -platform-specific diff --git a/test/pkg/manifest/manifest.xml b/test/pkg/manifest/manifest.xml new file mode 100644 index 0000000000..acf4843ede --- /dev/null +++ b/test/pkg/manifest/manifest.xml @@ -0,0 +1,10 @@ + + + containerd-shim-runhcs-v1 + + + + + + + diff --git a/test/pkg/manifest/resource_windows_386.syso b/test/pkg/manifest/resource_windows_386.syso new file mode 100644 index 0000000000..c4ee2aa22a Binary files /dev/null and b/test/pkg/manifest/resource_windows_386.syso differ diff --git a/test/pkg/manifest/resource_windows_amd64.syso b/test/pkg/manifest/resource_windows_amd64.syso new file mode 100644 index 0000000000..5e5c9c9cee Binary files /dev/null and b/test/pkg/manifest/resource_windows_amd64.syso differ diff --git a/test/pkg/manifest/resource_windows_arm.syso b/test/pkg/manifest/resource_windows_arm.syso new file mode 100644 index 0000000000..1210c4eb8f Binary files /dev/null and b/test/pkg/manifest/resource_windows_arm.syso differ diff --git a/test/pkg/manifest/resource_windows_arm64.syso b/test/pkg/manifest/resource_windows_arm64.syso new file mode 100644 index 0000000000..83004b1b5d Binary files /dev/null and b/test/pkg/manifest/resource_windows_arm64.syso differ diff --git a/test/pkg/manifest/versioninfo.json b/test/pkg/manifest/versioninfo.json new file mode 100644 index 0000000000..7c4fddcf92 --- /dev/null +++ b/test/pkg/manifest/versioninfo.json @@ -0,0 +1,43 @@ +{ + "FixedFileInfo": { + "FileVersion": { + "Major": 1, + "Minor": 0, + "Patch": 0, + "Build": 0 + }, + "ProductVersion": { + "Major": 1, + "Minor": 0, + "Patch": 0, + "Build": 0 + }, + "FileFlagsMask": "3f", + "FileFlags ": "00", + "FileOS": "040004", + "FileType": "01", + "FileSubType": "00" + }, + "StringFileInfo": { + "Comments": "", + "CompanyName": "", + "FileDescription": "", + "FileVersion": "", + "InternalName": "", + "LegalCopyright": "", + "LegalTrademarks": "", + "OriginalFilename": "", + "PrivateBuild": "", + "ProductName": "", + "ProductVersion": "v1.0.0.0", + "SpecialBuild": "" + }, + "VarFileInfo": { + "Translation": { + "LangID": "0409", + "CharsetID": "04B0" + } + }, + "IconPath": "", + "ManifestPath": "manifest.xml" +} \ No newline at end of file diff --git a/test/pkg/require/build.go b/test/pkg/require/build.go new file mode 100644 index 0000000000..260fb6f535 --- /dev/null +++ b/test/pkg/require/build.go @@ -0,0 +1,25 @@ +//go:build windows + +package require + +import ( + "testing" + + "github.com/Microsoft/hcsshim/osversion" + + _ "github.com/Microsoft/hcsshim/test/pkg/manifest" // manifest test binary automatically +) + +func Build(tb testing.TB, b uint16) { + tb.Helper() + if osversion.Build() < b { + tb.Skipf("Requires build %d+", b) + } +} + +func ExactBuild(tb testing.TB, b uint16) { + tb.Helper() + if osversion.Build() != b { + tb.Skipf("Requires exact build %d", b) + } +} diff --git a/test/pkg/require/requires.go b/test/pkg/require/requires.go new file mode 100644 index 0000000000..4ad499f4d6 --- /dev/null +++ b/test/pkg/require/requires.go @@ -0,0 +1,117 @@ +package require + +import ( + "errors" + "fmt" + "os" + "os/exec" + "path/filepath" + "testing" + + "github.com/Microsoft/hcsshim/test/pkg/flag" +) + +// Features checks the wanted features are present in given, +// and skips the test if any are missing or explicitly excluded. +// If the given set is empty, the function returns +// (by default, all features are enabled). +// +// See [flag.NewFeatureFlag] and [flag.IncludeExcludeStringSet] for more details. +func Features(tb testing.TB, given *flag.IncludeExcludeStringSet, want ...string) { + tb.Helper() + if given.Len() == 0 { + return + } + for _, f := range want { + if !given.IsSet(f) { + tb.Skipf("skipping test due to feature not set: %s", f) + } + } +} + +// AnyFeature checks if at least one of the features are enabled. +// +// See [Features] for more information. +func AnyFeature(tb testing.TB, given *flag.IncludeExcludeStringSet, want ...string) { + tb.Helper() + if given.Len() == 0 { + return + } + for _, f := range want { + if given.IsSet(f) { + return + } + } + tb.Skipf("skipping test due to missing features: %s", want) +} + +// Binary tries to locate `binary` in the PATH (or the current working directory), +// or the same the same directory as the currently-executing binary. +// +// Returns full binary path if it exists, otherwise, skips the test. +func BinaryInPath(tb testing.TB, binary string) string { + tb.Helper() + + if path, err := exec.LookPath(binary); err == nil || errors.Is(err, exec.ErrDot) { + p, found, err := fileExists(path) + if found { + return p + } + tb.Logf("did not find binary %q at path %q: %v", binary, path, err) + } else { + tb.Logf("could not look up binary %q: %v", binary, err) + } + + return Binary(tb, binary) +} + +// Binary checks if `binary` exists in the same directory as the test +// binary. +// Returns full binary path if it exists, otherwise, skips the test. +func Binary(tb testing.TB, binary string) string { + tb.Helper() + + executable, err := os.Executable() + if err != nil { + tb.Skipf("error locating executable: %v", err) + return "" + } + + baseDir := filepath.Dir(executable) + return File(tb, baseDir, binary) +} + +// File checks if `file` exists in `path`, and returns the full path +// if it exists. Otherwise, it skips the test. +func File(tb testing.TB, path, file string) string { + tb.Helper() + + p, found, err := fileExists(filepath.Join(path, file)) + if err != nil { + tb.Fatal(err) + } else if !found { + tb.Skipf("file %q not found in %q", file, path) + } + return p +} + +// fileExists checks if path points to a valid file. +func fileExists(path string) (string, bool, error) { + path, err := filepath.Abs(path) + if err != nil { + return "", false, fmt.Errorf("could not resolve path %q: %w", path, err) + } + + fi, err := os.Stat(path) + switch { + case os.IsNotExist(err): + return path, false, nil + case err != nil: + return "", false, fmt.Errorf("could not stat file %q: %w", path, err) + case fi.IsDir(): + return "", false, fmt.Errorf("path %q is a directory", path) + default: + } + + return path, true, nil +}