Files
renovate/lib/modules/manager/gomod/extract.spec.ts
Sebastian Poxhofer 300e3a616c test: shrink oversized fixtures to representative examples (#45666)
* test: reduce oversized expectation literals to representative examples

* test: shrink oversized fixtures instead of trimming assertions

Cut the real-world fixtures down to a representative set of entries that still covers every parser shape, and assert the complete output again with a single toEqual.
2026-09-04 10:40:45 +00:00

764 lines
23 KiB
TypeScript

import { codeBlock } from 'common-tags';
import { describe } from 'vitest';
import { Fixtures } from '~test/fixtures.ts';
import { getDefaultVersioning } from '../../datasource/common.ts';
import * as allVersioning from '../../versioning/index.ts';
import { convertGoDirectiveToSemVerRange } from './extract.ts';
import { extractPackageFile } from './index.ts';
const gomod1 = Fixtures.get('1/go-mod');
const gomod2 = Fixtures.get('2/go-mod');
describe('modules/manager/gomod/extract', () => {
describe('extractPackageFile()', () => {
it('returns null for empty', () => {
expect(extractPackageFile('nothing here')).toBeNull();
});
it('extracts single-line requires', () => {
const res = extractPackageFile(gomod1);
expect(res).toEqual({
deps: [
{
datasource: 'go',
depType: 'require',
depName: 'github.com/pkg/errors',
currentValue: 'v0.7.0',
managerData: { lineNumber: 2 },
},
{
datasource: 'go',
depType: 'require',
depName: 'github.com/aws/aws-sdk-go',
currentValue: 'v1.15.21',
managerData: { lineNumber: 3 },
},
{
datasource: 'go',
depType: 'indirect',
depName: 'github.com/davecgh/go-spew',
currentValue: 'v1.0.0',
enabled: false,
managerData: { lineNumber: 4 },
},
{
datasource: 'go',
depType: 'require',
depName: 'golang.org/x/foo',
currentValue: 'v1.0.0',
managerData: { lineNumber: 5 },
},
{
datasource: 'go',
depType: 'require',
depName: 'github.com/rarkins/foo',
currentValue: 'abcdef1',
skipReason: 'invalid-version',
managerData: { lineNumber: 6 },
},
{
datasource: 'go',
depType: 'require',
depName: 'gopkg.in/russross/blackfriday.v1',
currentValue: 'v1.0.0',
managerData: { lineNumber: 7 },
},
{
datasource: 'go',
depType: 'require',
depName: 'github.com/Azure/azure-sdk-for-go',
currentValue: 'v25.1.0+incompatible',
managerData: { lineNumber: 8 },
},
{
datasource: 'go',
depType: 'replace',
depName: '../errors',
skipReason: 'local-dependency',
managerData: { lineNumber: 10 },
},
{
datasource: 'go',
depType: 'replace',
depName: 'github.com/pravesht/gocql',
currentValue: 'v0.0.0',
managerData: { lineNumber: 11 },
},
{
datasource: 'go',
depType: 'require',
depName: 'github.com/caarlos0/env',
currentValue: 'v3.5.0+incompatible',
managerData: { lineNumber: 13 },
},
{
datasource: 'go',
depType: 'require',
depName: 'sigs.k8s.io/structured-merge-diff/v4',
currentValue: 'v4.7.0',
managerData: { lineNumber: 15 },
},
{
datasource: 'go',
depType: 'require',
depName: 'github.com/cucumber/common/messages/go/v18',
currentValue: 'v18.0.0',
managerData: { lineNumber: 16 },
},
],
});
});
it('extracts multi-line requires', () => {
const res = extractPackageFile(gomod2);
expect(res).toEqual({
deps: [
{
datasource: 'go',
depType: 'require',
depName: 'github.com/davecgh/go-spew',
currentValue: 'v1.1.0',
managerData: { multiLine: true, lineNumber: 3 },
},
{
datasource: 'go',
depType: 'require',
depName: 'github.com/emirpasic/gods',
currentValue: 'v1.9.0',
managerData: { multiLine: true, lineNumber: 4 },
},
{
datasource: 'go',
depType: 'require',
depName: 'github.com/fatih/color',
currentValue: 'v1.7.0',
managerData: { multiLine: true, lineNumber: 5 },
},
{
datasource: 'go',
depType: 'require',
depName: 'github.com/spf13/jwalterweatherman',
currentValue: 'v0.0.0-20180814060501-14d3d4c51834',
currentDigest: '14d3d4c51834',
digestOneAndOnly: true,
versioning: 'loose',
managerData: { multiLine: true, lineNumber: 6 },
},
{
datasource: 'go',
depType: 'require',
depName: 'github.com/src-d/gcfg/v2',
currentValue: 'v2.3.0',
managerData: { multiLine: true, lineNumber: 7 },
},
{
datasource: 'go',
depType: 'require',
depName: 'golang.org/x/text',
currentValue: 'v0.3.0',
managerData: { multiLine: true, lineNumber: 8 },
},
{
datasource: 'go',
depType: 'require',
depName: 'gopkg.in/src-d/go-billy.v4',
currentValue: 'v4.2.0',
managerData: { multiLine: true, lineNumber: 9 },
},
{
datasource: 'go',
depType: 'indirect',
depName: 'golang.org/x/net',
currentValue: 'v0.0.0-20191003171128-d98b1b443823',
currentDigest: 'd98b1b443823',
digestOneAndOnly: true,
versioning: 'loose',
enabled: false,
managerData: { multiLine: true, lineNumber: 10 },
},
],
});
});
it('ignores empty spaces in multi-line requires', () => {
const goMod = codeBlock`
module github.com/renovate-tests/gomod
go 1.19
require (
cloud.google.com/go v0.45.1
github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5 // indirect
)
`;
const res = extractPackageFile(goMod)?.deps;
expect(res).toHaveLength(3);
});
it('extracts replace directives from multi-line and single line', () => {
const goMod = codeBlock`
module github.com/renovate-tests/gomod
go 1.23
replace golang.org/x/foo => github.com/pravesht/gocql v0.0.0
replace (
k8s.io/client-go => k8s.io/client-go v0.21.9
)
replace (
k8s.io/cloud-provider => k8s.io/cloud-provider v0.17.3
k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.17.3 // indirect
k8s.io/code-generator => k8s.io/code-generator v0.17.3
)
`;
const res = extractPackageFile(goMod);
expect(res).toEqual({
deps: [
{
managerData: {
lineNumber: 1,
},
depName: 'go',
depType: 'golang',
currentValue: '1.23',
datasource: 'golang-version',
versioning: 'go-mod-directive',
commitMessageTopic: 'go module directive',
},
{
managerData: {
lineNumber: 2,
},
depName: 'github.com/pravesht/gocql',
depType: 'replace',
currentValue: 'v0.0.0',
datasource: 'go',
},
{
managerData: {
lineNumber: 4,
multiLine: true,
},
depName: 'k8s.io/client-go',
depType: 'replace',
currentValue: 'v0.21.9',
datasource: 'go',
},
{
managerData: {
lineNumber: 7,
multiLine: true,
},
depName: 'k8s.io/cloud-provider',
depType: 'replace',
currentValue: 'v0.17.3',
datasource: 'go',
},
{
managerData: {
lineNumber: 8,
multiLine: true,
},
depName: 'k8s.io/cluster-bootstrap',
depType: 'indirect',
enabled: false,
currentValue: 'v0.17.3',
datasource: 'go',
},
{
managerData: {
lineNumber: 9,
multiLine: true,
},
depName: 'k8s.io/code-generator',
depType: 'replace',
currentValue: 'v0.17.3',
datasource: 'go',
},
],
extractedConstraints: {
'%goMod': '~1.23.x',
},
constraintsVersioning: {
'%goMod': 'semver-coerced',
},
});
});
it('extracts replace directives from non-public module path', () => {
const goMod = codeBlock`
module github.com/JamieTanna-Mend-testing/tka-9783-golang-pro-main
go 1.25.5
require pro-lib v0.0.0-00010101000000-000000000000
replace pro-lib => github.com/ns-rpro-dev-tests/golang-pro-lib/libs/src/ns v0.0.0-20260219031232-e6910bd8fb97
`;
const res = extractPackageFile(goMod);
expect(res).toEqual({
deps: [
{
managerData: {
lineNumber: 1,
},
depName: 'go',
depType: 'golang',
currentValue: '1.25.5',
datasource: 'golang-version',
versioning: 'go-mod-directive',
commitMessageTopic: 'go module directive',
},
{
managerData: {
lineNumber: 2,
},
depName: 'pro-lib',
depType: 'require',
currentValue: 'v0.0.0-00010101000000-000000000000',
currentDigest: '000000000000',
datasource: 'go',
digestOneAndOnly: true,
versioning: 'loose',
skipReason: 'invalid-version',
},
{
managerData: {
lineNumber: 3,
},
depName: 'github.com/ns-rpro-dev-tests/golang-pro-lib/libs/src/ns',
depType: 'replace',
currentValue: 'v0.0.0-20260219031232-e6910bd8fb97',
currentDigest: 'e6910bd8fb97',
datasource: 'go',
digestOneAndOnly: true,
versioning: 'loose',
},
],
extractedConstraints: {
'%goMod': '~1.25.x',
},
constraintsVersioning: {
'%goMod': 'semver-coerced',
},
});
});
// https://go.dev/doc/modules/gomod-ref#exclude
it('ignores exclude directives from multi-line and single line', () => {
const goMod = codeBlock`
module github.com/renovate-tests/gomod
exclude github.com/pravesht/gocql v0.0.0
exclude (
k8s.io/client-go v0.21.9
)
exclude (
k8s.io/cloud-provider v0.17.3
k8s.io/cluster-bootstrap v0.17.3 // indirect
k8s.io/code-generator v0.17.3
)
`;
const res = extractPackageFile(goMod);
expect(res).toBeNull();
});
it('extracts the toolchain directive', () => {
const goMod = codeBlock`
module github.com/renovate-tests/gomod
go 1.23
toolchain go1.23.3
replace golang.org/x/foo => github.com/pravesht/gocql v0.0.0
`;
const res = extractPackageFile(goMod);
expect(res).toEqual({
deps: [
{
managerData: {
lineNumber: 1,
},
depName: 'go',
depType: 'golang',
currentValue: '1.23',
datasource: 'golang-version',
versioning: 'go-mod-directive',
commitMessageTopic: 'go module directive',
},
{
managerData: {
lineNumber: 2,
},
depName: 'go',
depType: 'toolchain',
currentValue: '1.23.3',
datasource: 'golang-version',
commitMessageTopic: 'go toolchain directive',
},
{
managerData: {
lineNumber: 3,
},
depName: 'github.com/pravesht/gocql',
depType: 'replace',
currentValue: 'v0.0.0',
datasource: 'go',
},
],
extractedConstraints: {
'%goMod': '~1.23.x',
golang: '1.23.3',
},
constraintsVersioning: {
'%goMod': 'semver-coerced',
},
});
});
it('extracts single-line tool directives', () => {
const goMod = codeBlock`
require github.com/oapi-codegen/oapi-codegen/v2 v2.4.1 // indirect
tool github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen
`;
const res = extractPackageFile(goMod);
expect(res).toEqual({
deps: [
{
datasource: 'go',
depName: 'github.com/oapi-codegen/oapi-codegen/v2',
depType: 'indirect',
currentValue: 'v2.4.1',
managerData: { lineNumber: 0 },
},
],
});
});
it('extracts multi-line tool directives', () => {
const goMod = codeBlock`
require github.com/oapi-codegen/oapi-codegen/v2 v2.4.1 // indirect
tool (
github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen
)
`;
const res = extractPackageFile(goMod);
expect(res).toEqual({
deps: [
{
datasource: 'go',
depName: 'github.com/oapi-codegen/oapi-codegen/v2',
depType: 'indirect',
currentValue: 'v2.4.1',
managerData: { lineNumber: 0 },
},
],
});
});
});
it('extracts tool directives with required modules', () => {
const goMod = codeBlock`
require github.com/oapi-codegen/oapi-codegen/v2 v2.4.1
tool github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen
`;
const res = extractPackageFile(goMod);
expect(res).toEqual({
deps: [
{
datasource: 'go',
depName: 'github.com/oapi-codegen/oapi-codegen/v2',
depType: 'require',
currentValue: 'v2.4.1',
managerData: { lineNumber: 0 },
},
],
});
});
it('extracts tool directives of sub-modules', () => {
const goMod = codeBlock`
require (
github.com/foo/bar v1.2.3
github.com/foo/bar/sub1/sub2 v4.5.6 // indirect
github.com/foo/bar/sub1 v7.8.9 // indirect
github.com/foo/bar/sub1/sub2/cmd/hell v10.11.12 // indirect
)
tool github.com/foo/bar/sub1/sub2/cmd/hello
`;
const res = extractPackageFile(goMod);
expect(res).toEqual({
deps: [
{
datasource: 'go',
depName: 'github.com/foo/bar',
depType: 'require',
currentValue: 'v1.2.3',
managerData: { lineNumber: 1, multiLine: true },
},
{
datasource: 'go',
depName: 'github.com/foo/bar/sub1/sub2',
depType: 'indirect',
currentValue: 'v4.5.6',
managerData: { lineNumber: 2, multiLine: true },
},
{
datasource: 'go',
depName: 'github.com/foo/bar/sub1',
depType: 'indirect',
currentValue: 'v7.8.9',
enabled: false,
managerData: { lineNumber: 3, multiLine: true },
},
{
datasource: 'go',
depName: 'github.com/foo/bar/sub1/sub2/cmd/hell',
depType: 'indirect',
currentValue: 'v10.11.12',
enabled: false,
managerData: { lineNumber: 4, multiLine: true },
},
],
});
});
it('extracts tool directives with exact match', () => {
const goMod = codeBlock`
require github.com/foo/bar v1.2.3 // indirect
tool github.com/foo/bar
`;
const res = extractPackageFile(goMod);
expect(res).toEqual({
deps: [
{
datasource: 'go',
depName: 'github.com/foo/bar',
depType: 'indirect',
currentValue: 'v1.2.3',
managerData: { lineNumber: 0 },
},
],
});
});
it('extracts tool directives with no matching dependencies', () => {
const goMod = codeBlock`
tool github.com/foo/bar/sub/cmd/hello
`;
const res = extractPackageFile(goMod);
expect(res).toBeNull();
});
/**
*
* https://go.dev/doc/modules/gomod-ref#retract
* https://go.dev/doc/modules/gomod-ref#godebug
*/
it('ignores directives unrelated to dependencies', () => {
const goMod = codeBlock`
module github.com/renovate-tests/gomod
godebug asynctimerchan=0
godebug (
default=go1.21
panicnil=1
)
retract v3.0.0
retract [v2.0.0,v2.0.5] // Build broken on some platforms.
retract (
v1.0.0 // Published accidentally.
v1.0.1 // Contains retractions only.
)
`;
const res = extractPackageFile(goMod);
expect(res).toBeNull();
});
it('marks placeholder pseudo versions with skipReason invalid-version', () => {
const goMod = codeBlock`
module github.com/renovate-tests/gomod
go 1.19
require (
github.com/foo/bar v1.2.3
github.com/baz/qux v0.0.0-00010101000000-000000000000
github.com/example/local v0.0.0-00010101000000-000000000000 // indirect
github.com/non/placeholder v1.2.4-0.20230101120000-abcdef123456
monorepo v0.0.0-00010101000000-000000000000
)
`;
const res = extractPackageFile(goMod);
expect(res).toEqual({
deps: [
{
managerData: {
lineNumber: 1,
},
depName: 'go',
depType: 'golang',
currentValue: '1.19',
datasource: 'golang-version',
versioning: 'go-mod-directive',
commitMessageTopic: 'go module directive',
},
{
managerData: {
lineNumber: 3,
multiLine: true,
},
depName: 'github.com/foo/bar',
depType: 'require',
currentValue: 'v1.2.3',
datasource: 'go',
},
{
managerData: {
lineNumber: 4,
multiLine: true,
},
depName: 'github.com/baz/qux',
depType: 'require',
currentValue: 'v0.0.0-00010101000000-000000000000',
datasource: 'go',
skipReason: 'invalid-version',
currentDigest: '000000000000',
digestOneAndOnly: true,
versioning: 'loose',
},
{
managerData: {
lineNumber: 5,
multiLine: true,
},
depName: 'github.com/example/local',
depType: 'indirect',
currentValue: 'v0.0.0-00010101000000-000000000000',
datasource: 'go',
skipReason: 'invalid-version',
enabled: false,
currentDigest: '000000000000',
digestOneAndOnly: true,
versioning: 'loose',
},
{
managerData: {
lineNumber: 6,
multiLine: true,
},
depName: 'github.com/non/placeholder',
depType: 'require',
currentValue: 'v1.2.4-0.20230101120000-abcdef123456',
datasource: 'go',
currentDigest: 'abcdef123456',
digestOneAndOnly: true,
versioning: 'loose',
},
{
managerData: {
lineNumber: 7,
multiLine: true,
},
depName: 'monorepo',
depType: 'require',
currentValue: 'v0.0.0-00010101000000-000000000000',
datasource: 'go',
currentDigest: '000000000000',
digestOneAndOnly: true,
versioning: 'loose',
skipReason: 'invalid-version',
},
],
extractedConstraints: {
'%goMod': '~1.19.x',
},
constraintsVersioning: {
'%goMod': 'semver-coerced',
},
});
});
it.each(['1.19', '1.19.0', '1.19.5'])(
'extracts `go` directive %s as a `%goMod` extracted constraint as a SemVer-minor compatible range',
(goDirective) => {
const goMod = codeBlock`
module github.com/renovate-tests/gomod
go ${goDirective}
`;
const res = extractPackageFile(goMod);
expect(res).toEqual({
deps: [
{
managerData: {
lineNumber: 1,
},
depName: 'go',
depType: 'golang',
currentValue: goDirective,
datasource: 'golang-version',
versioning: 'go-mod-directive',
commitMessageTopic: 'go module directive',
},
],
extractedConstraints: {
// NOTE that this is extracted as a range for the whole SemVer minor version
'%goMod': '~1.19.x',
},
constraintsVersioning: {
'%goMod': 'semver-coerced',
},
});
},
);
describe('the extracted version can be used as a SemVer constraint', () => {
const goMod = codeBlock`
module github.com/renovate-tests/gomod
go 1.19
`;
const res = extractPackageFile(goMod);
const datasourceVersioningName = getDefaultVersioning(
res!.deps[0].datasource,
);
const versioningName = res!.constraintsVersioning!['%goMod'];
const versioning = allVersioning.get(versioningName);
const constraint = res!.extractedConstraints!['%goMod']!;
it('extracts the expected versioning and constraints', () => {
// NOTE that this is not the `go-mod-directive` versioning, as that comes from `constraintsVersioning`
expect(datasourceVersioningName).toEqual('semver');
expect(res!.constraintsVersioning).toBeDefined();
expect(versioningName).toBeDefined();
expect(versioning).toBeDefined();
});
it(`${constraint} is a valid constraint`, () => {
expect(versioning.isValid(constraint)).toBeTrue();
});
it('matches version 1.19, even though it is not valid SemVer', () => {
expect(versioning.matches('1.19', constraint)).toBeTrue();
});
it('matches the current SemVer minor', () => {
expect(versioning.matches('1.19.0', constraint)).toBeTrue();
expect(versioning.matches('1.19.10', constraint)).toBeTrue();
});
it('does not match the next SemVer minor', () => {
expect(versioning.matches('1.20.0', constraint)).toBeFalse();
expect(versioning.matches('1.20.10', constraint)).toBeFalse();
});
it('does not match the previous SemVer minor', () => {
expect(versioning.matches('1.18.0', constraint)).toBeFalse();
expect(versioning.matches('1.18.5', constraint)).toBeFalse();
});
});
describe('convertGoDirectiveToSemVerRange()', () => {
it('handles undefined go directive', () => {
const goDirective = undefined;
const semVerRange = convertGoDirectiveToSemVerRange(goDirective);
expect(semVerRange.version).toBeUndefined();
});
});
});