Add comprehensive tests for Go and Python version conflict detection and licensing normalization

- Implemented GoVersionConflictDetectorTests to validate pseudo-version detection, conflict analysis, and conflict retrieval for Go modules.
- Created VersionConflictDetectorTests for Python to assess conflict detection across various version scenarios, including major, minor, and patch differences.
- Added SpdxLicenseNormalizerTests to ensure accurate normalization of SPDX license strings and classifiers.
- Developed VendoredPackageDetectorTests to identify vendored packages and extract embedded packages from Python packages, including handling of vendor directories and known vendored packages.
This commit is contained in:
StellaOps Bot
2025-12-07 01:51:37 +02:00
parent 98934170ca
commit e0f6efecce
66 changed files with 7591 additions and 451 deletions

View File

@@ -0,0 +1,205 @@
using StellaOps.Scanner.Analyzers.Lang.Go.Internal;
namespace StellaOps.Scanner.Analyzers.Lang.Go.Tests.Internal;
public sealed class GoCgoDetectorTests
{
[Fact]
public void AnalyzeGoFileContent_DetectsCgoImport()
{
var content = @"
package main
/*
#include <stdio.h>
*/
import ""C""
func main() {
C.puts(C.CString(""Hello from C""))
}
";
var result = GoCgoDetector.AnalyzeGoFileContent(content, "main.go");
Assert.True(result.HasCgoImport);
}
[Fact]
public void AnalyzeGoFileContent_DetectsCgoDirectives()
{
var content = @"
package main
/*
#cgo CFLAGS: -I/usr/local/include
#cgo LDFLAGS: -L/usr/local/lib -lpng
#cgo pkg-config: gtk+-3.0
#include <png.h>
*/
import ""C""
func main() {}
";
var result = GoCgoDetector.AnalyzeGoFileContent(content, "main.go");
Assert.True(result.HasCgoImport);
Assert.Equal(3, result.Directives.Count);
var cflags = result.Directives.FirstOrDefault(d => d.Type == "CFLAGS");
Assert.NotNull(cflags);
Assert.Equal("-I/usr/local/include", cflags.Value);
var ldflags = result.Directives.FirstOrDefault(d => d.Type == "LDFLAGS");
Assert.NotNull(ldflags);
Assert.Equal("-L/usr/local/lib -lpng", ldflags.Value);
var pkgconfig = result.Directives.FirstOrDefault(d => d.Type == "pkg-config");
Assert.NotNull(pkgconfig);
Assert.Equal("gtk+-3.0", pkgconfig.Value);
}
[Fact]
public void AnalyzeGoFileContent_DetectsIncludedHeaders()
{
var content = @"
package main
/*
#include <stdio.h>
#include <stdlib.h>
#include ""custom.h""
*/
import ""C""
func main() {}
";
var result = GoCgoDetector.AnalyzeGoFileContent(content, "main.go");
Assert.True(result.HasCgoImport);
Assert.Equal(3, result.Headers.Count);
Assert.Contains("stdio.h", result.Headers);
Assert.Contains("stdlib.h", result.Headers);
Assert.Contains("custom.h", result.Headers);
}
[Fact]
public void AnalyzeGoFileContent_DetectsPlatformConstrainedDirectives()
{
var content = @"
package main
/*
#cgo linux LDFLAGS: -lm
#cgo darwin LDFLAGS: -framework CoreFoundation
#cgo windows LDFLAGS: -lkernel32
*/
import ""C""
func main() {}
";
var result = GoCgoDetector.AnalyzeGoFileContent(content, "main.go");
Assert.True(result.HasCgoImport);
Assert.Equal(3, result.Directives.Count);
var linuxLdflags = result.Directives.FirstOrDefault(d => d.Constraint?.Contains("linux") == true);
Assert.NotNull(linuxLdflags);
Assert.Equal("-lm", linuxLdflags.Value);
var darwinLdflags = result.Directives.FirstOrDefault(d => d.Constraint?.Contains("darwin") == true);
Assert.NotNull(darwinLdflags);
Assert.Equal("-framework CoreFoundation", darwinLdflags.Value);
}
[Fact]
public void AnalyzeGoFileContent_NoCgoImport_ReturnsEmpty()
{
var content = @"
package main
import ""fmt""
func main() {
fmt.Println(""Hello"")
}
";
var result = GoCgoDetector.AnalyzeGoFileContent(content, "main.go");
Assert.False(result.HasCgoImport);
Assert.Empty(result.Directives);
Assert.Empty(result.Headers);
}
[Fact]
public void ExtractFromBuildSettings_ExtractsCgoEnabled()
{
var settings = new List<KeyValuePair<string, string?>>
{
new("CGO_ENABLED", "1"),
new("CGO_CFLAGS", "-I/usr/include"),
new("CGO_LDFLAGS", "-L/usr/lib -lssl"),
new("CC", "gcc"),
new("CXX", "g++"),
};
var result = GoCgoDetector.ExtractFromBuildSettings(settings);
Assert.True(result.CgoEnabled);
Assert.Equal("-I/usr/include", result.CgoFlags);
Assert.Equal("-L/usr/lib -lssl", result.CgoLdFlags);
Assert.Equal("gcc", result.CCompiler);
Assert.Equal("g++", result.CxxCompiler);
}
[Fact]
public void ExtractFromBuildSettings_CgoDisabled_ReturnsFalse()
{
var settings = new List<KeyValuePair<string, string?>>
{
new("CGO_ENABLED", "0"),
};
var result = GoCgoDetector.ExtractFromBuildSettings(settings);
Assert.False(result.CgoEnabled);
}
[Fact]
public void ExtractFromBuildSettings_NoSettings_ReturnsEmpty()
{
var settings = new List<KeyValuePair<string, string?>>();
var result = GoCgoDetector.ExtractFromBuildSettings(settings);
Assert.False(result.CgoEnabled);
Assert.True(result.IsEmpty);
}
[Fact]
public void CgoAnalysisResult_GetCFlags_CombinesMultipleDirectives()
{
var directives = new[]
{
new GoCgoDetector.CgoDirective("CFLAGS", "-I/usr/include", null, "a.go"),
new GoCgoDetector.CgoDirective("CFLAGS", "-I/usr/local/include", null, "b.go"),
};
var result = new GoCgoDetector.CgoAnalysisResult(
true,
["a.go", "b.go"],
[.. directives],
[],
[]);
var cflags = result.GetCFlags();
Assert.NotNull(cflags);
Assert.Contains("-I/usr/include", cflags);
Assert.Contains("-I/usr/local/include", cflags);
}
}

View File

@@ -0,0 +1,288 @@
using StellaOps.Scanner.Analyzers.Lang.Go.Internal;
namespace StellaOps.Scanner.Analyzers.Lang.Go.Tests.Internal;
public sealed class GoLicenseDetectorTests
{
[Fact]
public void AnalyzeLicenseContent_DetectsMitLicense()
{
var content = @"
MIT License
Copyright (c) 2023 Example Corp
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the ""Software""), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software...
";
var result = GoLicenseDetector.AnalyzeLicenseContent(content);
Assert.True(result.IsDetected);
Assert.Equal("MIT", result.SpdxIdentifier);
Assert.Equal(GoLicenseDetector.LicenseConfidence.Medium, result.Confidence);
}
[Fact]
public void AnalyzeLicenseContent_DetectsApache2License()
{
var content = @"
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
";
var result = GoLicenseDetector.AnalyzeLicenseContent(content);
Assert.True(result.IsDetected);
Assert.Equal("Apache-2.0", result.SpdxIdentifier);
Assert.Equal(GoLicenseDetector.LicenseConfidence.Medium, result.Confidence);
}
[Fact]
public void AnalyzeLicenseContent_DetectsBsd3ClauseLicense()
{
var content = @"
BSD 3-Clause License
Copyright (c) 2023, Example Corp
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
";
var result = GoLicenseDetector.AnalyzeLicenseContent(content);
Assert.True(result.IsDetected);
Assert.Equal("BSD-3-Clause", result.SpdxIdentifier);
Assert.Equal(GoLicenseDetector.LicenseConfidence.Medium, result.Confidence);
}
[Fact]
public void AnalyzeLicenseContent_DetectsGpl3License()
{
var content = @"
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
";
var result = GoLicenseDetector.AnalyzeLicenseContent(content);
Assert.True(result.IsDetected);
Assert.Equal("GPL-3.0-only", result.SpdxIdentifier);
Assert.Equal(GoLicenseDetector.LicenseConfidence.Medium, result.Confidence);
}
[Fact]
public void AnalyzeLicenseContent_DetectsIscLicense()
{
var content = @"
ISC License
Copyright (c) 2023, Example Corp
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
";
var result = GoLicenseDetector.AnalyzeLicenseContent(content);
Assert.True(result.IsDetected);
Assert.Equal("ISC", result.SpdxIdentifier);
Assert.Equal(GoLicenseDetector.LicenseConfidence.Medium, result.Confidence);
}
[Fact]
public void AnalyzeLicenseContent_DetectsUnlicense()
{
var content = @"
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
";
var result = GoLicenseDetector.AnalyzeLicenseContent(content);
Assert.True(result.IsDetected);
Assert.Equal("Unlicense", result.SpdxIdentifier);
Assert.Equal(GoLicenseDetector.LicenseConfidence.Medium, result.Confidence);
}
[Fact]
public void AnalyzeLicenseContent_DetectsSpdxIdentifier()
{
var content = @"
// SPDX-License-Identifier: Apache-2.0
Some license text here...
";
var result = GoLicenseDetector.AnalyzeLicenseContent(content);
Assert.True(result.IsDetected);
Assert.Equal("Apache-2.0", result.SpdxIdentifier);
Assert.Equal(GoLicenseDetector.LicenseConfidence.High, result.Confidence);
}
[Fact]
public void AnalyzeLicenseContent_DetectsDualLicenseSpdx()
{
var content = @"
// SPDX-License-Identifier: MIT OR Apache-2.0
Dual licensed under MIT and Apache 2.0
";
var result = GoLicenseDetector.AnalyzeLicenseContent(content);
Assert.True(result.IsDetected);
Assert.Equal("MIT OR Apache-2.0", result.SpdxIdentifier);
Assert.Equal(GoLicenseDetector.LicenseConfidence.High, result.Confidence);
}
[Fact]
public void AnalyzeLicenseContent_DetectsMpl2License()
{
var content = @"
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
";
var result = GoLicenseDetector.AnalyzeLicenseContent(content);
Assert.True(result.IsDetected);
Assert.Equal("MPL-2.0", result.SpdxIdentifier);
Assert.Equal(GoLicenseDetector.LicenseConfidence.Medium, result.Confidence);
}
[Fact]
public void AnalyzeLicenseContent_EmptyContent_ReturnsUnknown()
{
var result = GoLicenseDetector.AnalyzeLicenseContent("");
Assert.False(result.IsDetected);
Assert.Null(result.SpdxIdentifier);
Assert.Equal(GoLicenseDetector.LicenseConfidence.None, result.Confidence);
}
[Fact]
public void AnalyzeLicenseContent_UnrecognizedContent_ReturnsUnknown()
{
var content = @"
This is some custom proprietary license text that doesn't match any known patterns.
No redistribution allowed without express written permission.
";
var result = GoLicenseDetector.AnalyzeLicenseContent(content);
Assert.False(result.IsDetected);
Assert.Null(result.SpdxIdentifier);
}
[Fact]
public void AnalyzeLicenseContent_KeywordFallback_DetectsMit()
{
var content = @"
Some text mentioning MIT but not in the standard format
This project is licensed under MIT terms
";
var result = GoLicenseDetector.AnalyzeLicenseContent(content);
// Should detect MIT via keyword fallback with low confidence
Assert.True(result.IsDetected);
Assert.Equal("MIT", result.SpdxIdentifier);
Assert.Equal(GoLicenseDetector.LicenseConfidence.Low, result.Confidence);
}
[Fact]
public void LicenseInfo_Unknown_IsDetectedFalse()
{
var info = GoLicenseDetector.LicenseInfo.Unknown;
Assert.False(info.IsDetected);
Assert.Null(info.SpdxIdentifier);
Assert.Null(info.LicenseFile);
Assert.Equal(GoLicenseDetector.LicenseConfidence.None, info.Confidence);
}
[Fact]
public void AnalyzeLicenseContent_DetectsCC0License()
{
var content = @"
CC0 1.0 Universal
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP.
";
var result = GoLicenseDetector.AnalyzeLicenseContent(content);
Assert.True(result.IsDetected);
Assert.Equal("CC0-1.0", result.SpdxIdentifier);
}
[Fact]
public void AnalyzeLicenseContent_DetectsZlibLicense()
{
var content = @"
zlib License
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
";
var result = GoLicenseDetector.AnalyzeLicenseContent(content);
Assert.True(result.IsDetected);
Assert.Equal("Zlib", result.SpdxIdentifier);
}
[Fact]
public void AnalyzeLicenseContent_DetectsBoostLicense()
{
var content = @"
Boost Software License - Version 1.0 - August 17th, 2003
Permission is hereby granted, free of charge, to any person or organization
obtaining a copy of the software and accompanying documentation covered by
this license (the ""Software"") to use, reproduce, display, distribute,
execute, and transmit the Software...
";
var result = GoLicenseDetector.AnalyzeLicenseContent(content);
Assert.True(result.IsDetected);
Assert.Equal("BSL-1.0", result.SpdxIdentifier);
}
}

View File

@@ -0,0 +1,276 @@
using System.Collections.Immutable;
using StellaOps.Scanner.Analyzers.Lang.Go.Internal;
namespace StellaOps.Scanner.Analyzers.Lang.Go.Tests.Internal;
public sealed class GoVersionConflictDetectorTests
{
[Fact]
public void IsPseudoVersion_DetectsPseudoVersions()
{
// Standard pseudo-version formats
Assert.True(GoVersionConflictDetector.IsPseudoVersion("v0.0.0-20210101120000-abcdef123456"));
Assert.True(GoVersionConflictDetector.IsPseudoVersion("v1.2.3-0.20210101120000-abcdef123456"));
Assert.True(GoVersionConflictDetector.IsPseudoVersion("v0.0.0-20230915143052-deadbeef1234"));
}
[Fact]
public void IsPseudoVersion_RejectsRegularVersions()
{
Assert.False(GoVersionConflictDetector.IsPseudoVersion("v1.0.0"));
Assert.False(GoVersionConflictDetector.IsPseudoVersion("v1.2.3"));
Assert.False(GoVersionConflictDetector.IsPseudoVersion("v0.1.0-alpha"));
Assert.False(GoVersionConflictDetector.IsPseudoVersion("v2.0.0-beta.1"));
}
[Fact]
public void Analyze_DetectsPseudoVersionConflict()
{
var modules = new List<GoSourceInventory.GoSourceModule>
{
new()
{
Path = "github.com/example/mod",
Version = "v0.0.0-20210101120000-abcdef123456",
},
};
var result = GoVersionConflictDetector.Analyze(
modules,
[],
[],
ImmutableArray<string>.Empty);
Assert.True(result.HasConflicts);
Assert.Single(result.Conflicts);
Assert.Equal(GoVersionConflictDetector.GoConflictType.PseudoVersion, result.Conflicts[0].ConflictType);
Assert.Equal(GoVersionConflictDetector.GoConflictSeverity.Medium, result.Conflicts[0].Severity);
}
[Fact]
public void Analyze_DetectsReplaceOverrideConflict()
{
var modules = new List<GoSourceInventory.GoSourceModule>
{
new()
{
Path = "github.com/example/mod",
Version = "v1.0.0",
IsReplaced = true,
ReplacementPath = "github.com/fork/mod",
ReplacementVersion = "v1.1.0",
},
};
var result = GoVersionConflictDetector.Analyze(
modules,
[],
[],
ImmutableArray<string>.Empty);
Assert.True(result.HasConflicts);
Assert.Single(result.Conflicts);
Assert.Equal(GoVersionConflictDetector.GoConflictType.ReplaceOverride, result.Conflicts[0].ConflictType);
Assert.Equal(GoVersionConflictDetector.GoConflictSeverity.Low, result.Conflicts[0].Severity);
}
[Fact]
public void Analyze_DetectsLocalReplacementAsHighSeverity()
{
var modules = new List<GoSourceInventory.GoSourceModule>
{
new()
{
Path = "github.com/example/mod",
Version = "v1.0.0",
IsReplaced = true,
ReplacementPath = "../local/mod",
},
};
var result = GoVersionConflictDetector.Analyze(
modules,
[],
[],
ImmutableArray<string>.Empty);
Assert.True(result.HasConflicts);
Assert.Single(result.Conflicts);
Assert.Equal(GoVersionConflictDetector.GoConflictType.LocalReplacement, result.Conflicts[0].ConflictType);
Assert.Equal(GoVersionConflictDetector.GoConflictSeverity.High, result.Conflicts[0].Severity);
}
[Fact]
public void Analyze_DetectsExcludedVersionConflict()
{
var modules = new List<GoSourceInventory.GoSourceModule>
{
new()
{
Path = "github.com/example/mod",
Version = "v1.0.0",
},
};
var excludes = new List<GoModParser.GoModExclude>
{
new("github.com/example/mod", "v1.0.0"),
};
var result = GoVersionConflictDetector.Analyze(
modules,
[],
excludes,
ImmutableArray<string>.Empty);
Assert.True(result.HasConflicts);
Assert.Single(result.Conflicts);
Assert.Equal(GoVersionConflictDetector.GoConflictType.ExcludedVersion, result.Conflicts[0].ConflictType);
Assert.Equal(GoVersionConflictDetector.GoConflictSeverity.High, result.Conflicts[0].Severity);
}
[Fact]
public void Analyze_DetectsMajorVersionMismatch()
{
var modules = new List<GoSourceInventory.GoSourceModule>
{
new()
{
Path = "github.com/example/mod",
Version = "v1.0.0",
},
new()
{
Path = "github.com/example/mod/v2",
Version = "v2.0.0",
},
};
var result = GoVersionConflictDetector.Analyze(
modules,
[],
[],
ImmutableArray<string>.Empty);
Assert.True(result.HasConflicts);
Assert.Equal(2, result.Conflicts.Length);
Assert.All(result.Conflicts, c =>
Assert.Equal(GoVersionConflictDetector.GoConflictType.MajorVersionMismatch, c.ConflictType));
}
[Fact]
public void Analyze_NoConflicts_ReturnsEmpty()
{
var modules = new List<GoSourceInventory.GoSourceModule>
{
new()
{
Path = "github.com/example/mod",
Version = "v1.0.0",
},
new()
{
Path = "github.com/other/lib",
Version = "v2.1.0",
},
};
var result = GoVersionConflictDetector.Analyze(
modules,
[],
[],
ImmutableArray<string>.Empty);
Assert.False(result.HasConflicts);
Assert.Empty(result.Conflicts);
}
[Fact]
public void GetConflict_ReturnsConflictForModule()
{
var modules = new List<GoSourceInventory.GoSourceModule>
{
new()
{
Path = "github.com/example/mod",
Version = "v0.0.0-20210101120000-abcdef123456",
},
};
var result = GoVersionConflictDetector.Analyze(
modules,
[],
[],
ImmutableArray<string>.Empty);
var conflict = result.GetConflict("github.com/example/mod");
Assert.NotNull(conflict);
Assert.Equal("github.com/example/mod", conflict.ModulePath);
}
[Fact]
public void GetConflict_ReturnsNullForNonConflictingModule()
{
var modules = new List<GoSourceInventory.GoSourceModule>
{
new()
{
Path = "github.com/example/mod",
Version = "v1.0.0",
},
};
var result = GoVersionConflictDetector.Analyze(
modules,
[],
[],
ImmutableArray<string>.Empty);
var conflict = result.GetConflict("github.com/example/mod");
Assert.Null(conflict);
}
[Fact]
public void AnalyzeWorkspace_DetectsCrossModuleConflicts()
{
var inventory1 = new GoSourceInventory.SourceInventoryResult(
"github.com/workspace/mod1",
"1.21",
[
new GoSourceInventory.GoSourceModule
{
Path = "github.com/shared/dep",
Version = "v1.0.0",
},
],
ImmutableArray<string>.Empty,
GoVersionConflictDetector.GoConflictAnalysis.Empty,
GoCgoDetector.CgoAnalysisResult.Empty,
null);
var inventory2 = new GoSourceInventory.SourceInventoryResult(
"github.com/workspace/mod2",
"1.21",
[
new GoSourceInventory.GoSourceModule
{
Path = "github.com/shared/dep",
Version = "v1.2.0",
},
],
ImmutableArray<string>.Empty,
GoVersionConflictDetector.GoConflictAnalysis.Empty,
GoCgoDetector.CgoAnalysisResult.Empty,
null);
var result = GoVersionConflictDetector.AnalyzeWorkspace([inventory1, inventory2]);
Assert.True(result.HasConflicts);
Assert.Single(result.Conflicts);
Assert.Equal(GoVersionConflictDetector.GoConflictType.WorkspaceConflict, result.Conflicts[0].ConflictType);
Assert.Contains("v1.0.0", result.Conflicts[0].RequestedVersions);
Assert.Contains("v1.2.0", result.Conflicts[0].RequestedVersions);
}
}