executors: Provide more context when the semantic version is invalid (#55024)

The annoying error of `Invalid Semantic Version` tells us nothing and
makes troubleshooting the issue difficult with lots of churn.

I have update Executor usage of `semvar.NewVersion` to wrap the error
with a message that include the actual version that causes the error.
This will help us understand the specific version string that is causing
the error and more quickly determine _why_ the version is getting
injected.

## Test plan

Update/add Go tests.
This commit is contained in:
Randell Callahan 2023-07-17 13:59:46 -06:00 committed by GitHub
parent ea88de6697
commit 5a3fda4bd6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 322 additions and 195 deletions

View File

@ -323,6 +323,7 @@ go_library(
"//internal/version",
"//internal/version/upgradestore",
"//internal/webhooks/outbound",
"//lib/api",
"//lib/batches",
"//lib/errors",
"//lib/output",

View File

@ -11,6 +11,8 @@ import (
"github.com/sourcegraph/sourcegraph/internal/gqlutil"
"github.com/sourcegraph/sourcegraph/internal/types"
"github.com/sourcegraph/sourcegraph/internal/version"
"github.com/sourcegraph/sourcegraph/lib/api"
"github.com/sourcegraph/sourcegraph/lib/errors"
)
const oneReleaseCycle = 35 * 24 * time.Hour
@ -122,12 +124,12 @@ func calculateExecutorCompatibility(ev string) (*string, error) {
return compatibility.ToGraphQL(), nil
}
s, err := semver.NewVersion(sv)
s, err := getSemVer("sourcegraph", sv)
if err != nil {
return nil, err
}
e, err := semver.NewVersion(ev)
e, err := getSemVer("executor", ev)
if err != nil {
return nil, err
}
@ -145,3 +147,24 @@ func calculateExecutorCompatibility(ev string) (*string, error) {
return compatibility.ToGraphQL(), nil
}
func getSemVer(source string, version string) (*semver.Version, error) {
v, err := semver.NewVersion(version)
if err != nil {
// Maybe the version is a daily build and need to extract the version from there.
// We don't care about the error from getDailyBuildVersion because we already have the error.
v, _ = getDailyBuildVersion(version)
if v == nil {
return nil, errors.Wrapf(err, "failed to parse %s version %q", source, version)
}
}
return v, nil
}
func getDailyBuildVersion(version string) (*semver.Version, error) {
matches := api.BuildDateRegex.FindStringSubmatch(version)
if len(matches) > 2 {
return semver.NewVersion(matches[2])
}
return nil, nil
}

View File

@ -1,183 +1,236 @@
package graphqlbackend
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/sourcegraph/sourcegraph/internal/version"
)
func TestExecutorResolver(t *testing.T) {
t.Run("isExecutorOutdated", func(t *testing.T) {
testCases := []struct {
executorVersion string
sourcegraphVersion string
isActive bool
expected *string
description string
}{
// The executor isn't outdated when in dev mode.
{
executorVersion: "0.0.0+dev",
sourcegraphVersion: "0.0.0+dev",
isActive: true,
expected: nil,
description: "executor and the Sourcegraph instance are in dev mode",
},
// The executor isn't outdated when it's inactive
{
executorVersion: "0.0.0+dev",
sourcegraphVersion: "0.0.0+dev",
isActive: false,
expected: nil,
description: "executor is inactive",
},
// The executor is not outdated if it's one minor version behind the sourcegraph version (SEMVER)
{
executorVersion: "3.43.0",
sourcegraphVersion: "3.42.0",
isActive: true,
expected: ExecutorCompatibilityUpToDate.ToGraphQL(),
description: "executor is one version ahead of the Sourcegraph instance",
},
// The executor is not outdated if it's one minor version ahead of the sourcegraph version (SEMVER)
{
executorVersion: "3.42.0",
sourcegraphVersion: "3.43.0",
isActive: true,
expected: ExecutorCompatibilityUpToDate.ToGraphQL(),
description: "executor is one minor version ahead of the Sourcegraph instance",
},
// The executor is not outdated when both sourcegraph and executor are the same (SEMVER).
{
executorVersion: "3.43.0",
sourcegraphVersion: "3.43.0",
isActive: true,
expected: ExecutorCompatibilityUpToDate.ToGraphQL(),
description: "executor and the sourcegraph instance are the same version (SEMVER)",
},
// The executor is not outdated when both sourcegraph and executor are the same (insiders).
{
executorVersion: "executor-patch-notest-es-ignite-debug_168065_2022-08-25_e94e18c4ebcc_patch",
sourcegraphVersion: "169135_2022-08-25_4.4-a2b623dce148",
isActive: true,
expected: ExecutorCompatibilityUpToDate.ToGraphQL(),
description: "executor and the sourcegraph instance are the same version (insiders)",
},
{
executorVersion: "executor-patch-notest-es-ignite-debug_168065_2022-08-25_e94e18c4ebcc_patch",
sourcegraphVersion: "169135_2022-08-25_a2b623dce148",
isActive: true,
expected: ExecutorCompatibilityUpToDate.ToGraphQL(),
description: "executor and the sourcegraph instance are the same version (insiders - old version)",
},
// The executor is outdated if the sourcegraph version is more than one version
// greater than the executor version (SEMVER).
{
executorVersion: "3.40.0",
sourcegraphVersion: "3.43.0",
isActive: true,
expected: ExecutorCompatibilityOutdated.ToGraphQL(),
description: "executor is more than one minor version behind the Sourcegraph instance (SEMVER)",
},
{
executorVersion: "3.43.0",
sourcegraphVersion: "4.0.0",
isActive: true,
expected: ExecutorCompatibilityOutdated.ToGraphQL(),
description: "Sourcegraph instance is a major version ahead of the executor",
},
// The executor is too new if the executor is more than one version ahead of the sourcegraph version.
{
executorVersion: "3.43.0",
sourcegraphVersion: "3.40.0",
isActive: true,
expected: ExecutorCompatibilityVersionAhead.ToGraphQL(),
description: "executor is more than one minor version ahead of the Sourcegraph instance (SEMVER)",
},
{
executorVersion: "4.0.0",
sourcegraphVersion: "3.43.0",
isActive: true,
expected: ExecutorCompatibilityVersionAhead.ToGraphQL(),
description: "executor is a major version ahead of the Sourcegraph instance",
},
// The executor is outdated if the sourcegraph version is greater than the executor version (insiders).
{
executorVersion: "executor-patch-notest-es-ignite-debug_168065_2022-06-10_e94e18c4ebcc_patch",
sourcegraphVersion: "169135_2022-07-25_a2b623dce148",
isActive: true,
expected: ExecutorCompatibilityOutdated.ToGraphQL(),
description: "executor version is less than the Sourcegraph build date - one release cycle (insiders)",
},
// The executor is too new if the executor version is greater than the one release cycle + sourcegraph build date (insiders)
{
executorVersion: "executor-patch-notest-es-ignite-debug_168065_2022-10-30_e94e18c4ebcc_patch",
sourcegraphVersion: "169135_2022-09-15_a2b623dce148",
isActive: true,
expected: ExecutorCompatibilityVersionAhead.ToGraphQL(),
description: "executor version is greater than the one release cycle + Sourcegraph build date (insiders)",
},
// The executor is up to date if the build date isn't greater than one release cycle + sourcegraph build date (insiders)
{
executorVersion: "executor-patch-notest-es-ignite-debug_168065_2022-08-20_e94e18c4ebcc_patch",
sourcegraphVersion: "169135_2022-08-15_a2b623dce148",
isActive: true,
expected: ExecutorCompatibilityUpToDate.ToGraphQL(),
description: "executor version is a few days ahead of the Sourcegraph instance (insiders)",
},
// version mismatch
{
executorVersion: "3.36.2",
sourcegraphVersion: "169135_2022-08-15_a2b623dce148",
isActive: true,
expected: nil,
description: "Sourcegraph instance is on insiders version while the executor is tagged",
},
{
executorVersion: "169135_2022-08-15_a2b623dce148",
sourcegraphVersion: "3.39.2",
isActive: true,
expected: nil,
description: "executor is on insiders version while the Sourcegraph instance is tagged",
},
{
executorVersion: "0.0.0+dev",
sourcegraphVersion: "3.39.2",
isActive: true,
expected: nil,
description: "executor is in dev mode while Sourcegraph instance is tagged",
},
{
executorVersion: "3.39.2",
sourcegraphVersion: "0.0.0+dev",
isActive: true,
expected: nil,
description: "Sourcegraph instance is in dev mode while executor is tagged",
},
{
executorVersion: "0.0.0+dev",
sourcegraphVersion: "169135_2022-08-15_a2b623dce148",
isActive: true,
expected: nil,
description: "executor is in dev mode while Sourcegraph instance is on insiders version",
},
{
executorVersion: "169135_2022-08-15_a2b623dce148",
sourcegraphVersion: "0.0.0+dev",
isActive: true,
expected: nil,
description: "Sourcegraph instance is in dev mode while executor is on insiders version",
},
}
func TestCalculateExecutorCompatibility(t *testing.T) {
tests := []struct {
name string
executorVersion string
sourcegraphVersion string
isActive bool
expectedCompatibility ExecutorCompatibility
expectedError error
}{
{
name: "Dev mode",
executorVersion: "0.0.0+dev",
sourcegraphVersion: "0.0.0+dev",
isActive: true,
expectedCompatibility: "",
},
{
name: "Executor is inactive",
executorVersion: "0.0.0+dev",
sourcegraphVersion: "0.0.0+dev",
isActive: false,
expectedCompatibility: "",
},
{
name: "Executor is one minor version behind",
executorVersion: "3.43.0",
sourcegraphVersion: "3.42.0",
isActive: true,
expectedCompatibility: ExecutorCompatibilityUpToDate,
},
{
name: "Executor is one minor version behind",
executorVersion: "3.42.0",
sourcegraphVersion: "3.43.0",
isActive: true,
expectedCompatibility: ExecutorCompatibilityUpToDate,
},
{
name: "Executor is the same version as the Sourcegraph instance",
executorVersion: "3.43.0",
sourcegraphVersion: "3.43.0",
isActive: true,
expectedCompatibility: ExecutorCompatibilityUpToDate,
},
{
name: "Executor is the same version as the Sourcegraph instance (insiders)",
executorVersion: "executor-patch-notest-es-ignite-debug_168065_2022-08-25_e94e18c4ebcc_patch",
sourcegraphVersion: "169135_2022-08-25_4.4-a2b623dce148",
isActive: true,
expectedCompatibility: ExecutorCompatibilityUpToDate,
},
{
name: "Executor is the same version as the Sourcegraph instance (insiders - old version)",
executorVersion: "executor-patch-notest-es-ignite-debug_168065_2022-08-25_e94e18c4ebcc_patch",
sourcegraphVersion: "169135_2022-08-25_a2b623dce148",
isActive: true,
expectedCompatibility: ExecutorCompatibilityUpToDate,
},
{
name: "Executor is multiple minor versions behind",
executorVersion: "3.40.0",
sourcegraphVersion: "3.43.0",
isActive: true,
expectedCompatibility: ExecutorCompatibilityOutdated,
},
{
name: "Executor is major version behind",
executorVersion: "3.43.0",
sourcegraphVersion: "4.0.0",
isActive: true,
expectedCompatibility: ExecutorCompatibilityOutdated,
},
{
name: "Executor is multiple patch versions behind",
executorVersion: "3.43.0",
sourcegraphVersion: "3.43.12",
isActive: true,
expectedCompatibility: ExecutorCompatibilityUpToDate,
},
{
name: "Executor is multiple minor version ahead",
executorVersion: "3.43.0",
sourcegraphVersion: "3.40.0",
isActive: true,
expectedCompatibility: ExecutorCompatibilityVersionAhead,
},
{
executorVersion: "4.0.0",
sourcegraphVersion: "3.43.0",
isActive: true,
expectedCompatibility: ExecutorCompatibilityVersionAhead,
},
{
name: "Executor is one release cycle behind (insiders)",
executorVersion: "executor-patch-notest-es-ignite-debug_168065_2022-06-10_e94e18c4ebcc_patch",
sourcegraphVersion: "169135_2022-07-25_a2b623dce148",
isActive: true,
expectedCompatibility: ExecutorCompatibilityOutdated,
},
{
name: "Executor is one release cycle ahead (insiders)",
executorVersion: "executor-patch-notest-es-ignite-debug_168065_2022-10-30_e94e18c4ebcc_patch",
sourcegraphVersion: "169135_2022-09-15_a2b623dce148",
isActive: true,
expectedCompatibility: ExecutorCompatibilityVersionAhead,
},
{
name: "Execcutor build date is greater than one release cycle + sourcegraph build date (insiders)",
executorVersion: "executor-patch-notest-es-ignite-debug_168065_2022-08-20_e94e18c4ebcc_patch",
sourcegraphVersion: "169135_2022-08-15_a2b623dce148",
isActive: true,
expectedCompatibility: ExecutorCompatibilityUpToDate,
},
{
name: "Sourcegrpah version mismatch",
executorVersion: "3.36.2",
sourcegraphVersion: "169135_2022-08-15_a2b623dce148",
isActive: true,
expectedCompatibility: "",
},
{
name: "Executor version mismatch",
executorVersion: "169135_2022-08-15_a2b623dce148",
sourcegraphVersion: "3.39.2",
isActive: true,
expectedCompatibility: "",
},
{
name: "Executor is in dev mode",
executorVersion: "0.0.0+dev",
sourcegraphVersion: "3.39.2",
isActive: true,
expectedCompatibility: "",
},
{
name: "Sourcegraph instance is in dev mode",
executorVersion: "3.39.2",
sourcegraphVersion: "0.0.0+dev",
isActive: true,
expectedCompatibility: "",
},
{
name: "Executor is in dev mode and Sourcegraph instance is on insiders version",
executorVersion: "0.0.0+dev",
sourcegraphVersion: "169135_2022-08-15_a2b623dce148",
isActive: true,
expectedCompatibility: "",
},
{
name: "Sourcegraph instance is in dev mode and executor is on insiders version",
executorVersion: "169135_2022-08-15_a2b623dce148",
sourcegraphVersion: "0.0.0+dev",
isActive: true,
expectedCompatibility: "",
},
{
name: "Executor version is an invalid semver",
executorVersion: "\n1.2",
sourcegraphVersion: "3.39.2",
isActive: true,
expectedCompatibility: "",
expectedError: errors.New("failed to parse executor version \"\\n1.2\": Invalid Semantic Version"),
},
{
name: "Sourcegraph version is an invalid semver",
executorVersion: "4.0.1",
sourcegraphVersion: "\n1.2",
isActive: true,
expectedCompatibility: "",
expectedError: errors.New("failed to parse sourcegraph version \"\\n1.2\": Invalid Semantic Version"),
},
{
name: "Executor release branch build",
executorVersion: "5.1_231128_2023-06-27_5.0-7ac9ba347103",
sourcegraphVersion: "5.0.3",
isActive: true,
expectedCompatibility: ExecutorCompatibilityUpToDate,
},
{
name: "Sourcegraph release branch build",
executorVersion: "5.0.3",
sourcegraphVersion: "5.1_231128_2023-06-27_5.0-7ac9ba347103",
isActive: true,
expectedCompatibility: ExecutorCompatibilityUpToDate,
},
{
name: "Executor release candidate",
executorVersion: "5.1.3-rc.1",
sourcegraphVersion: "5.1.3",
isActive: true,
expectedCompatibility: ExecutorCompatibilityUpToDate,
},
{
name: "Executor version missing patch",
executorVersion: "5.1",
sourcegraphVersion: "5.1.3",
isActive: true,
expectedCompatibility: ExecutorCompatibilityUpToDate,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
version.Mock(test.sourcegraphVersion)
actual, err := calculateExecutorCompatibility(test.executorVersion)
for _, tc := range testCases {
version.Mock(tc.sourcegraphVersion)
want, err := calculateExecutorCompatibility(tc.executorVersion)
assert.NoError(t, err)
assert.Equal(t, tc.expected, want, tc.description)
}
})
if test.expectedError != nil {
require.Error(t, err)
assert.Equal(t, test.expectedError.Error(), err.Error())
assert.Nil(t, actual)
} else {
require.NoError(t, err)
// Once https://github.com/stretchr/testify/pull/1287 is merged, we can remove this and just use Equal.
// When they are not equal we are just given the addresses which doesn't mean much to us, and tell us
// how to fix the test.
if test.expectedCompatibility != "" {
require.NotNil(t, actual)
assert.Equal(t, test.expectedCompatibility, ExecutorCompatibility(*actual))
} else {
assert.Nil(t, actual)
}
}
})
}
}

View File

@ -127,7 +127,7 @@ func (h *handler[T]) dequeue(ctx context.Context, queueName string, metadata exe
var err error
version2Supported, err = api.CheckSourcegraphVersion(metadata.version, "4.3.0-0", "2022-11-24")
if err != nil {
return executortypes.Job{}, false, err
return executortypes.Job{}, false, errors.Wrapf(err, "failed to check version %q", metadata.version)
}
}

View File

@ -76,7 +76,7 @@ func TestHandler_HandleDequeue(t *testing.T) {
name: "Invalid version",
body: `{"executorName": "test-executor", "version":"\n1.2", "numCPUs": 1, "memory": "1GB", "diskSpace": "10GB"}`,
expectedStatusCode: http.StatusInternalServerError,
expectedResponseBody: `{"error":"Invalid Semantic Version"}`,
expectedResponseBody: `{"error":"failed to check version \"\\n1.2\": Invalid Semantic Version"}`,
assertionFunc: func(t *testing.T, mockStore *dbworkerstoremocks.MockStore[testRecord], jobTokenStore *executorstore.MockJobTokenStore) {
require.Len(t, mockStore.DequeueFunc.History(), 0)
require.Len(t, jobTokenStore.CreateFunc.History(), 0)

View File

@ -91,7 +91,7 @@ func (m *MultiHandler) dequeue(ctx context.Context, req executortypes.DequeueReq
var err error
version2Supported, err = api.CheckSourcegraphVersion(req.Version, "4.3.0-0", "2022-11-24")
if err != nil {
return executortypes.Job{}, false, err
return executortypes.Job{}, false, errors.Wrapf(err, "failed to check version %q", req.Version)
}
}

View File

@ -235,7 +235,7 @@ func TestMultiHandler_HandleDequeue(t *testing.T) {
dequeueEvents: []dequeueEvent{
{
expectedStatusCode: http.StatusInternalServerError,
expectedResponseBody: `{"error":"Invalid Semantic Version"}`,
expectedResponseBody: `{"error":"failed to check version \"\\n1.2\": Invalid Semantic Version"}`,
},
},
},

View File

@ -17,4 +17,9 @@ go_test(
timeout = "short",
srcs = ["version_check_test.go"],
embed = [":api"],
deps = [
"//lib/errors",
"@com_github_stretchr_testify//assert",
"@com_github_stretchr_testify//require",
],
)

View File

@ -6,8 +6,10 @@ import (
"github.com/Masterminds/semver"
)
var buildDate = regexp.MustCompile(`\d+_(\d{4}-\d{2}-\d{2})_(\d+\.\d+-)?[a-z0-9]{7,}(_patch)?$`)
// BuildDateRegex matches the build date in a Sourcegraph version string.
var BuildDateRegex = regexp.MustCompile(`\d+_(\d{4}-\d{2}-\d{2})_(\d+\.\d+)?-?[a-z0-9]{7,}(_patch)?$`)
// CheckSourcegraphVersion checks if the given version satisfies the given constraint.
// NOTE: A version with a prerelease suffix (e.g. the "-rc.3" of "3.35.1-rc.3") is not
// considered by semver to satisfy a constraint without a prerelease suffix, regardless of
// whether or not the major/minor/patch version is greater than or equal to that of the
@ -28,7 +30,7 @@ func CheckSourcegraphVersion(version, constraint, minDate string) (bool, error)
// version string, we match on 7 or more characters. Currently, the Sourcegraph version
// is expected to return 12:
// https://sourcegraph.com/github.com/sourcegraph/sourcegraph/-/blob/enterprise/dev/ci/internal/ci/config.go?L96.
matches := buildDate.FindStringSubmatch(version)
matches := BuildDateRegex.FindStringSubmatch(version)
if len(matches) > 1 {
return matches[1] >= minDate, nil
}

View File

@ -1,126 +1,169 @@
package api
import "testing"
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/sourcegraph/sourcegraph/lib/errors"
)
func TestCheckSourcegraphVersion(t *testing.T) {
for _, tc := range []struct {
currentVersion, constraint, minDate string
expected bool
tests := []struct {
name string
currentVersion string
constraint string
minDate string
expected bool
expectedErr error
}{
{
name: "Version matches constraint",
currentVersion: "3.12.6",
constraint: ">= 3.12.6",
minDate: "2020-01-19",
expected: true,
},
{
name: "Release candidate version matches constraint",
currentVersion: "3.12.6-rc.1",
constraint: ">= 3.12.6-0",
minDate: "2020-01-19",
expected: true,
},
{
name: "Newer release candidate version matches constraint",
currentVersion: "3.12.6-rc.3",
constraint: ">= 3.10.6-0",
minDate: "2020-01-19",
expected: true,
},
{
name: "Version does not match constraint",
currentVersion: "3.12.6",
constraint: ">= 3.13",
minDate: "2020-01-19",
expected: false,
},
{
name: "Constraint without patch version",
currentVersion: "3.13.0",
constraint: ">= 3.13",
minDate: "2020-01-19",
expected: true,
},
{
name: "Dev version",
currentVersion: "dev",
constraint: ">= 3.13",
minDate: "2020-01-19",
expected: true,
},
{
name: "Newer dev version",
currentVersion: "0.0.0+dev",
constraint: ">= 3.13",
minDate: "2020-01-19",
expected: true,
},
// 7-character abbreviated hash
{
name: "Seven character abbreviated hash",
currentVersion: "54959_2020-01-29_9258595",
minDate: "2020-01-19",
constraint: ">= 999.13",
expected: true,
},
{
name: "Seven character abbreviated hash too old",
currentVersion: "54959_2020-01-29_9258595",
minDate: "2020-01-30",
constraint: ">= 999.13",
expected: false,
},
{
name: "Seven character abbreviated hash matches date",
currentVersion: "54959_2020-01-29_9258595",
minDate: "2020-01-29",
constraint: ">= 0.0",
expected: true,
},
// 12-character abbreviated hash
{
name: "Twelve character abbreviated hash",
currentVersion: "54959_2020-01-29_925859585436",
minDate: "2020-01-19",
constraint: ">= 999.13",
expected: true,
},
{
name: "Twelve character abbreviated hash too old",
currentVersion: "54959_2020-01-29_925859585436",
minDate: "2020-01-30",
constraint: ">= 999.13",
expected: false,
},
{
name: "Twelve character abbreviated hash matches date",
currentVersion: "54959_2020-01-29_925859585436",
minDate: "2020-01-29",
constraint: ">= 0.0",
expected: true,
},
// 12-character abbreviated hash with latest version tag
{
name: "Twelve character abbreviated hash with tag",
currentVersion: "54959_2020-01-29_4.4-925859585436",
minDate: "2020-01-19",
constraint: ">= 999.13",
expected: true,
},
{
name: "Twelve character abbreviated hash with tag too old and does not match constraint",
currentVersion: "54959_2020-01-29_4.4-925859585436",
minDate: "2020-01-30",
constraint: ">= 999.13",
expected: false,
},
{
name: "Twelve character abbreviated hash with tag matches date",
currentVersion: "54959_2020-01-29_4.4-925859585436",
minDate: "2020-01-29",
constraint: ">= 0.0",
expected: true,
},
// Full 40-character hash, just for fun
{
name: "Forty character hash",
currentVersion: "54959_2020-01-29_7db7d396346284fd0f8f79f130f38b16fb1d3d70",
minDate: "2020-01-29",
constraint: ">= 0.0",
expected: true,
},
} {
actual, err := CheckSourcegraphVersion(tc.currentVersion, tc.constraint, tc.minDate)
if err != nil {
t.Errorf("err: %s", err)
}
{
name: "Daily release build",
currentVersion: "5.1_231128_2023-06-27_5.0-7ac9ba347103",
minDate: "2020-01-29",
constraint: ">= 4.4",
expected: true,
},
{
name: "Invalid semantic version",
currentVersion: "\n1.2",
minDate: "2020-01-29",
constraint: ">= 0.0",
expected: false,
expectedErr: errors.New("Invalid Semantic Version"),
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
actual, err := CheckSourcegraphVersion(test.currentVersion, test.constraint, test.minDate)
if actual != tc.expected {
t.Errorf("wrong result. want=%t, got=%t (version=%q, constraint=%q)", tc.expected, actual, tc.currentVersion, tc.constraint)
}
if test.expectedErr != nil {
require.Error(t, err)
assert.Equal(t, test.expectedErr.Error(), err.Error())
} else {
require.NoError(t, err)
assert.Equal(t, test.expected, actual)
}
})
}
}