feat(python-analyzer): Enhance deterministic output tests and add new fixtures
- Updated TASKS.md to reflect changes in test fixtures for SCAN-PY-405-007. - Added multiple test cases to ensure deterministic output for various Python package scenarios, including conda environments, requirements files, and vendored directories. - Created new expected output files for conda packages (numpy, requests) and updated existing test fixtures for container whiteouts, wheel workspaces, and zipapp embedded requirements. - Introduced helper methods to create wheel and zipapp packages for testing purposes. - Added metadata files for new test fixtures to validate package detection and dependencies.
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "numpy",
|
||||
"version": "1.26.0",
|
||||
"depends": [
|
||||
"python >=3.11"
|
||||
],
|
||||
"files": [
|
||||
"numpy/__init__.py"
|
||||
],
|
||||
"requested": true
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "requests",
|
||||
"version": "2.31.0",
|
||||
"depends": [
|
||||
"python >=3.11",
|
||||
"urllib3 >=1.26"
|
||||
],
|
||||
"files": [
|
||||
"requests/__init__.py"
|
||||
],
|
||||
"requested": false
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
[
|
||||
{
|
||||
"analyzerId": "python",
|
||||
"componentKey": "purl::pkg:pypi/numpy@1.26.0",
|
||||
"purl": "pkg:pypi/numpy@1.26.0",
|
||||
"name": "numpy",
|
||||
"version": "1.26.0",
|
||||
"type": "pypi",
|
||||
"usedByEntrypoint": false,
|
||||
"metadata": {
|
||||
"pkg.confidence": "High",
|
||||
"pkg.kind": "Conda",
|
||||
"pkg.location": "conda-meta"
|
||||
},
|
||||
"evidence": [
|
||||
{
|
||||
"kind": "file",
|
||||
"source": "conda-meta",
|
||||
"locator": "conda-meta/numpy-1.26.0-0.json"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"analyzerId": "python",
|
||||
"componentKey": "purl::pkg:pypi/requests@2.31.0",
|
||||
"purl": "pkg:pypi/requests@2.31.0",
|
||||
"name": "requests",
|
||||
"version": "2.31.0",
|
||||
"type": "pypi",
|
||||
"usedByEntrypoint": false,
|
||||
"metadata": {
|
||||
"pkg.confidence": "High",
|
||||
"pkg.kind": "Conda",
|
||||
"pkg.location": "conda-meta"
|
||||
},
|
||||
"evidence": [
|
||||
{
|
||||
"kind": "file",
|
||||
"source": "conda-meta",
|
||||
"locator": "conda-meta/requests-2.31.0-0.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,36 @@
|
||||
[
|
||||
{
|
||||
"analyzerId": "python",
|
||||
"componentKey": "purl::pkg:pypi/visible@2.0.0",
|
||||
"purl": "pkg:pypi/visible@2.0.0",
|
||||
"name": "visible",
|
||||
"version": "2.0.0",
|
||||
"type": "pypi",
|
||||
"usedByEntrypoint": false,
|
||||
"metadata": {
|
||||
"distInfoPath": "layers/layer1/usr/lib/python3.11/site-packages/visible-2.0.0.dist-info",
|
||||
"name": "visible",
|
||||
"normalizedName": "visible",
|
||||
"pkg.confidence": "High",
|
||||
"pkg.kind": "Wheel",
|
||||
"pkg.location": "layers/layer1/usr/lib/python3.11/site-packages/visible-2.0.0.dist-info",
|
||||
"provenance": "dist-info",
|
||||
"record.hashMismatches": "0",
|
||||
"record.hashedEntries": "0",
|
||||
"record.ioErrors": "0",
|
||||
"record.missingFiles": "0",
|
||||
"record.totalEntries": "0",
|
||||
"runtime.libPaths.count": "2",
|
||||
"runtime.versions": "3.11",
|
||||
"summary": "Visible after overlay",
|
||||
"version": "2.0.0"
|
||||
},
|
||||
"evidence": [
|
||||
{
|
||||
"kind": "file",
|
||||
"source": "METADATA",
|
||||
"locator": "layers/layer1/usr/lib/python3.11/site-packages/visible-2.0.0.dist-info/METADATA"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,5 @@
|
||||
Metadata-Version: 2.1
|
||||
Name: whited
|
||||
Version: 1.0.0
|
||||
Summary: Should be removed by whiteout
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
Metadata-Version: 2.1
|
||||
Name: visible
|
||||
Version: 2.0.0
|
||||
Summary: Visible after overlay
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"_meta": {
|
||||
"sources": []
|
||||
},
|
||||
"default": {
|
||||
"requests": {
|
||||
"version": "==2.28.0"
|
||||
}
|
||||
},
|
||||
"develop": {
|
||||
"pytest": {
|
||||
"version": "==7.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
[
|
||||
{
|
||||
"analyzerId": "python",
|
||||
"componentKey": "purl::pkg:pypi/pytest@7.0.0",
|
||||
"purl": "pkg:pypi/pytest@7.0.0",
|
||||
"name": "pytest",
|
||||
"version": "7.0.0",
|
||||
"type": "pypi",
|
||||
"usedByEntrypoint": false,
|
||||
"metadata": {
|
||||
"declaredOnly": "true",
|
||||
"lockLocator": "Pipfile.lock",
|
||||
"lockSource": "Pipfile.lock",
|
||||
"pkg.confidence": "Medium",
|
||||
"pkg.kind": "DeclaredOnly",
|
||||
"pkg.location": "Pipfile.lock",
|
||||
"scope": "dev"
|
||||
},
|
||||
"evidence": [
|
||||
{
|
||||
"kind": "metadata",
|
||||
"source": "Pipfile.lock",
|
||||
"locator": "Pipfile.lock"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"analyzerId": "python",
|
||||
"componentKey": "purl::pkg:pypi/requests@2.28.0",
|
||||
"purl": "pkg:pypi/requests@2.28.0",
|
||||
"name": "requests",
|
||||
"version": "2.28.0",
|
||||
"type": "pypi",
|
||||
"usedByEntrypoint": false,
|
||||
"metadata": {
|
||||
"declaredOnly": "true",
|
||||
"lockLocator": "Pipfile.lock",
|
||||
"lockSource": "Pipfile.lock",
|
||||
"pkg.confidence": "Medium",
|
||||
"pkg.kind": "DeclaredOnly",
|
||||
"pkg.location": "Pipfile.lock",
|
||||
"scope": "prod"
|
||||
},
|
||||
"evidence": [
|
||||
{
|
||||
"kind": "metadata",
|
||||
"source": "Pipfile.lock",
|
||||
"locator": "Pipfile.lock"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,3 @@
|
||||
[project]
|
||||
name = "editable-src"
|
||||
version = "0.0.0"
|
||||
@@ -0,0 +1,106 @@
|
||||
[
|
||||
{
|
||||
"analyzerId": "python",
|
||||
"componentKey": "explicit::python::pypi::editable-src::sha256:5bd6cf3f7ac212830a9fcbc83b06ab72e79bafc1c94ea98a3d0560547c96c923",
|
||||
"name": "editable-src",
|
||||
"type": "pypi",
|
||||
"usedByEntrypoint": false,
|
||||
"metadata": {
|
||||
"declared.locator": "requirements.txt",
|
||||
"declared.scope": "prod",
|
||||
"declared.source": "requirements.txt",
|
||||
"declared.sourceType": "editable",
|
||||
"declared.versionSpec": "./editable-src",
|
||||
"declaredOnly": "true",
|
||||
"lockEditablePath": "./editable-src",
|
||||
"lockLocator": "requirements.txt",
|
||||
"lockSource": "requirements.txt",
|
||||
"pkg.confidence": "Medium",
|
||||
"pkg.kind": "DeclaredOnly",
|
||||
"pkg.location": "requirements.txt",
|
||||
"scope": "prod"
|
||||
},
|
||||
"evidence": [
|
||||
{
|
||||
"kind": "metadata",
|
||||
"source": "requirements.txt",
|
||||
"locator": "requirements.txt"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"analyzerId": "python",
|
||||
"componentKey": "purl::pkg:pypi/certifi@2022.12.7",
|
||||
"purl": "pkg:pypi/certifi@2022.12.7",
|
||||
"name": "certifi",
|
||||
"version": "2022.12.7",
|
||||
"type": "pypi",
|
||||
"usedByEntrypoint": false,
|
||||
"metadata": {
|
||||
"declaredOnly": "true",
|
||||
"lockLocator": "requirements-base.txt",
|
||||
"lockSource": "requirements-base.txt",
|
||||
"pkg.confidence": "Medium",
|
||||
"pkg.kind": "DeclaredOnly",
|
||||
"pkg.location": "requirements-base.txt",
|
||||
"scope": "prod"
|
||||
},
|
||||
"evidence": [
|
||||
{
|
||||
"kind": "metadata",
|
||||
"source": "requirements-base.txt",
|
||||
"locator": "requirements-base.txt"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"analyzerId": "python",
|
||||
"componentKey": "purl::pkg:pypi/requests@2.28.0",
|
||||
"purl": "pkg:pypi/requests@2.28.0",
|
||||
"name": "requests",
|
||||
"version": "2.28.0",
|
||||
"type": "pypi",
|
||||
"usedByEntrypoint": false,
|
||||
"metadata": {
|
||||
"declaredOnly": "true",
|
||||
"lockLocator": "requirements.txt",
|
||||
"lockSource": "requirements.txt",
|
||||
"pkg.confidence": "Medium",
|
||||
"pkg.kind": "DeclaredOnly",
|
||||
"pkg.location": "requirements.txt",
|
||||
"scope": "prod"
|
||||
},
|
||||
"evidence": [
|
||||
{
|
||||
"kind": "metadata",
|
||||
"source": "requirements.txt",
|
||||
"locator": "requirements.txt"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"analyzerId": "python",
|
||||
"componentKey": "purl::pkg:pypi/urllib3@1.26.0",
|
||||
"purl": "pkg:pypi/urllib3@1.26.0",
|
||||
"name": "urllib3",
|
||||
"version": "1.26.0",
|
||||
"type": "pypi",
|
||||
"usedByEntrypoint": false,
|
||||
"metadata": {
|
||||
"declaredOnly": "true",
|
||||
"lockLocator": "requirements-base.txt",
|
||||
"lockSource": "requirements-base.txt",
|
||||
"pkg.confidence": "Medium",
|
||||
"pkg.kind": "DeclaredOnly",
|
||||
"pkg.location": "requirements-base.txt",
|
||||
"scope": "prod"
|
||||
},
|
||||
"evidence": [
|
||||
{
|
||||
"kind": "metadata",
|
||||
"source": "requirements-base.txt",
|
||||
"locator": "requirements-base.txt"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,2 @@
|
||||
urllib3==1.26.0
|
||||
certifi==2022.12.7
|
||||
@@ -0,0 +1,3 @@
|
||||
requests==2.28.0
|
||||
-r requirements-base.txt
|
||||
-e ./editable-src
|
||||
@@ -0,0 +1,65 @@
|
||||
[
|
||||
{
|
||||
"analyzerId": "python",
|
||||
"componentKey": "explicit::python::pypi::urllib3::sha256:aa29f86a6e70276f0f1bc8b4fc71abb66af19af7dbcf4bfe40b40c3c4aa08467",
|
||||
"purl": "pkg:pypi/urllib3@1.26.0",
|
||||
"name": "urllib3",
|
||||
"version": "1.26.0",
|
||||
"type": "pypi",
|
||||
"usedByEntrypoint": false,
|
||||
"metadata": {
|
||||
"embedded": "true",
|
||||
"embedded.confidence": "High",
|
||||
"embedded.parentPackage": "requests",
|
||||
"embedded.parentVersion": "2.0.0",
|
||||
"embedded.path": "requests/_vendor/urllib3",
|
||||
"embedded.versionSource": "heuristic"
|
||||
},
|
||||
"evidence": [
|
||||
{
|
||||
"kind": "file",
|
||||
"source": "vendored",
|
||||
"locator": "lib/python3.11/site-packages/requests/_vendor/urllib3/__init__.py"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"analyzerId": "python",
|
||||
"componentKey": "purl::pkg:pypi/requests@2.0.0",
|
||||
"purl": "pkg:pypi/requests@2.0.0",
|
||||
"name": "requests",
|
||||
"version": "2.0.0",
|
||||
"type": "pypi",
|
||||
"usedByEntrypoint": false,
|
||||
"metadata": {
|
||||
"distInfoPath": "lib/python3.11/site-packages/requests-2.0.0.dist-info",
|
||||
"name": "requests",
|
||||
"normalizedName": "requests",
|
||||
"pkg.confidence": "High",
|
||||
"pkg.kind": "Wheel",
|
||||
"pkg.location": "lib/python3.11/site-packages/requests-2.0.0.dist-info",
|
||||
"provenance": "dist-info",
|
||||
"record.hashMismatches": "0",
|
||||
"record.hashedEntries": "0",
|
||||
"record.ioErrors": "0",
|
||||
"record.missingFiles": "0",
|
||||
"record.totalEntries": "0",
|
||||
"runtime.libPaths.count": "1",
|
||||
"runtime.versions": "3.11",
|
||||
"summary": "Parent package with vendored deps",
|
||||
"vendored.confidence": "High",
|
||||
"vendored.detected": "true",
|
||||
"vendored.packageCount": "1",
|
||||
"vendored.packages": "urllib3@1.26.0",
|
||||
"vendored.paths": "requests/_vendor",
|
||||
"version": "2.0.0"
|
||||
},
|
||||
"evidence": [
|
||||
{
|
||||
"kind": "file",
|
||||
"source": "METADATA",
|
||||
"locator": "lib/python3.11/site-packages/requests-2.0.0.dist-info/METADATA"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,5 @@
|
||||
Metadata-Version: 2.1
|
||||
Name: requests
|
||||
Version: 2.0.0
|
||||
Summary: Parent package with vendored deps
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
__version__ = "2.0.0"
|
||||
@@ -0,0 +1 @@
|
||||
__version__ = "1.26.0"
|
||||
@@ -0,0 +1,51 @@
|
||||
[
|
||||
{
|
||||
"analyzerId": "python",
|
||||
"componentKey": "purl::pkg:pypi/wheelpkg@1.0.0",
|
||||
"purl": "pkg:pypi/wheelpkg@1.0.0",
|
||||
"name": "wheelpkg",
|
||||
"version": "1.0.0",
|
||||
"type": "pypi",
|
||||
"usedByEntrypoint": false,
|
||||
"metadata": {
|
||||
"distInfoPath": "archives/wheel/wheelpkg-1.0.0-py3-none-any.whl/wheelpkg-1.0.0.dist-info",
|
||||
"name": "wheelpkg",
|
||||
"normalizedName": "wheelpkg",
|
||||
"pkg.confidence": "Definitive",
|
||||
"pkg.kind": "Wheel",
|
||||
"pkg.location": "archives/wheel/wheelpkg-1.0.0-py3-none-any.whl",
|
||||
"provenance": "dist-info",
|
||||
"record.hashMismatches": "0",
|
||||
"record.hashedEntries": "3",
|
||||
"record.ioErrors": "0",
|
||||
"record.missingFiles": "0",
|
||||
"record.totalEntries": "4",
|
||||
"summary": "Wheel fixture",
|
||||
"version": "1.0.0",
|
||||
"wheel.generator": "stellaops-test",
|
||||
"wheel.rootIsPurelib": "true",
|
||||
"wheel.tags": "py3-none-any",
|
||||
"wheel.version": "1.0"
|
||||
},
|
||||
"evidence": [
|
||||
{
|
||||
"kind": "file",
|
||||
"source": "METADATA",
|
||||
"locator": "dist/wheelpkg-1.0.0-py3-none-any.whl",
|
||||
"value": "wheelpkg-1.0.0.dist-info/METADATA"
|
||||
},
|
||||
{
|
||||
"kind": "file",
|
||||
"source": "RECORD",
|
||||
"locator": "dist/wheelpkg-1.0.0-py3-none-any.whl",
|
||||
"value": "wheelpkg-1.0.0.dist-info/RECORD"
|
||||
},
|
||||
{
|
||||
"kind": "file",
|
||||
"source": "WHEEL",
|
||||
"locator": "dist/wheelpkg-1.0.0-py3-none-any.whl",
|
||||
"value": "wheelpkg-1.0.0.dist-info/WHEEL"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,41 @@
|
||||
[
|
||||
{
|
||||
"analyzerId": "python",
|
||||
"componentKey": "purl::pkg:pypi/hostpkg@0.1.0",
|
||||
"purl": "pkg:pypi/hostpkg@0.1.0",
|
||||
"name": "hostpkg",
|
||||
"version": "0.1.0",
|
||||
"type": "pypi",
|
||||
"usedByEntrypoint": false,
|
||||
"metadata": {
|
||||
"distInfoPath": "lib/python3.11/site-packages/hostpkg-0.1.0.dist-info",
|
||||
"name": "hostpkg",
|
||||
"normalizedName": "hostpkg",
|
||||
"pkg.confidence": "High",
|
||||
"pkg.kind": "Wheel",
|
||||
"pkg.location": "lib/python3.11/site-packages/hostpkg-0.1.0.dist-info",
|
||||
"provenance": "dist-info",
|
||||
"record.hashMismatches": "0",
|
||||
"record.hashedEntries": "0",
|
||||
"record.ioErrors": "0",
|
||||
"record.missingFiles": "0",
|
||||
"record.totalEntries": "0",
|
||||
"runtime.libPaths.count": "1",
|
||||
"runtime.versions": "3.11",
|
||||
"summary": "Host package for zipapp fixture",
|
||||
"version": "0.1.0",
|
||||
"zipapps.count": "1",
|
||||
"zipapps.detected": "true",
|
||||
"zipapps.embeddedDeps.count": "2",
|
||||
"zipapps.embeddedDeps.sample": "flask;requests",
|
||||
"zipapps.pythonVersions": "3.11"
|
||||
},
|
||||
"evidence": [
|
||||
{
|
||||
"kind": "file",
|
||||
"source": "METADATA",
|
||||
"locator": "lib/python3.11/site-packages/hostpkg-0.1.0.dist-info/METADATA"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,5 @@
|
||||
Metadata-Version: 2.1
|
||||
Name: hostpkg
|
||||
Version: 0.1.0
|
||||
Summary: Host package for zipapp fixture
|
||||
|
||||
@@ -85,6 +85,148 @@ public sealed class PythonLanguageAnalyzerTests
|
||||
usageHints);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CondaEnvFixtureProducesDeterministicOutputAsync()
|
||||
{
|
||||
var cancellationToken = TestContext.Current.CancellationToken;
|
||||
var fixturePath = TestPaths.ResolveFixture("lang", "python", "conda-env");
|
||||
var goldenPath = Path.Combine(fixturePath, "expected.json");
|
||||
|
||||
var analyzers = new ILanguageAnalyzer[]
|
||||
{
|
||||
new PythonLanguageAnalyzer()
|
||||
};
|
||||
|
||||
await LanguageAnalyzerTestHarness.AssertDeterministicAsync(
|
||||
fixturePath,
|
||||
goldenPath,
|
||||
analyzers,
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RequirementsIncludesEditableFixtureProducesDeterministicOutputAsync()
|
||||
{
|
||||
var cancellationToken = TestContext.Current.CancellationToken;
|
||||
var fixturePath = TestPaths.ResolveFixture("lang", "python", "requirements-includes-editable");
|
||||
var goldenPath = Path.Combine(fixturePath, "expected.json");
|
||||
|
||||
var analyzers = new ILanguageAnalyzer[]
|
||||
{
|
||||
new PythonLanguageAnalyzer()
|
||||
};
|
||||
|
||||
await LanguageAnalyzerTestHarness.AssertDeterministicAsync(
|
||||
fixturePath,
|
||||
goldenPath,
|
||||
analyzers,
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PipfileLockDefaultDevelopFixtureProducesDeterministicOutputAsync()
|
||||
{
|
||||
var cancellationToken = TestContext.Current.CancellationToken;
|
||||
var fixturePath = TestPaths.ResolveFixture("lang", "python", "pipfile-lock-default-develop");
|
||||
var goldenPath = Path.Combine(fixturePath, "expected.json");
|
||||
|
||||
var analyzers = new ILanguageAnalyzer[]
|
||||
{
|
||||
new PythonLanguageAnalyzer()
|
||||
};
|
||||
|
||||
await LanguageAnalyzerTestHarness.AssertDeterministicAsync(
|
||||
fixturePath,
|
||||
goldenPath,
|
||||
analyzers,
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WheelWorkspaceFixtureProducesDeterministicOutputAsync()
|
||||
{
|
||||
var cancellationToken = TestContext.Current.CancellationToken;
|
||||
var fixturePath = TestPaths.ResolveFixture("lang", "python", "wheel-workspace");
|
||||
var goldenPath = Path.Combine(fixturePath, "expected.json");
|
||||
|
||||
var distDir = Path.Combine(fixturePath, "dist");
|
||||
Directory.CreateDirectory(distDir);
|
||||
|
||||
var wheelPath = Path.Combine(distDir, "wheelpkg-1.0.0-py3-none-any.whl");
|
||||
CreateWheelpkgWheel(wheelPath);
|
||||
|
||||
var analyzers = new ILanguageAnalyzer[]
|
||||
{
|
||||
new PythonLanguageAnalyzer()
|
||||
};
|
||||
|
||||
await LanguageAnalyzerTestHarness.AssertDeterministicAsync(
|
||||
fixturePath,
|
||||
goldenPath,
|
||||
analyzers,
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ZipappEmbeddedRequirementsFixtureProducesDeterministicOutputAsync()
|
||||
{
|
||||
var cancellationToken = TestContext.Current.CancellationToken;
|
||||
var fixturePath = TestPaths.ResolveFixture("lang", "python", "zipapp-embedded-requirements");
|
||||
var goldenPath = Path.Combine(fixturePath, "expected.json");
|
||||
|
||||
var zipappPath = Path.Combine(fixturePath, "myapp.pyz");
|
||||
CreateZipappWithEmbeddedRequirements(zipappPath);
|
||||
|
||||
var analyzers = new ILanguageAnalyzer[]
|
||||
{
|
||||
new PythonLanguageAnalyzer()
|
||||
};
|
||||
|
||||
await LanguageAnalyzerTestHarness.AssertDeterministicAsync(
|
||||
fixturePath,
|
||||
goldenPath,
|
||||
analyzers,
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ContainerWhiteoutsFixtureProducesDeterministicOutputAsync()
|
||||
{
|
||||
var cancellationToken = TestContext.Current.CancellationToken;
|
||||
var fixturePath = TestPaths.ResolveFixture("lang", "python", "container-whiteouts");
|
||||
var goldenPath = Path.Combine(fixturePath, "expected.json");
|
||||
|
||||
var analyzers = new ILanguageAnalyzer[]
|
||||
{
|
||||
new PythonLanguageAnalyzer()
|
||||
};
|
||||
|
||||
await LanguageAnalyzerTestHarness.AssertDeterministicAsync(
|
||||
fixturePath,
|
||||
goldenPath,
|
||||
analyzers,
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task VendoredDirectoryFixtureProducesDeterministicOutputAsync()
|
||||
{
|
||||
var cancellationToken = TestContext.Current.CancellationToken;
|
||||
var fixturePath = TestPaths.ResolveFixture("lang", "python", "vendored-directory");
|
||||
var goldenPath = Path.Combine(fixturePath, "expected.json");
|
||||
|
||||
var analyzers = new ILanguageAnalyzer[]
|
||||
{
|
||||
new PythonLanguageAnalyzer()
|
||||
};
|
||||
|
||||
await LanguageAnalyzerTestHarness.AssertDeterministicAsync(
|
||||
fixturePath,
|
||||
goldenPath,
|
||||
analyzers,
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task LockfileCollectorEmitsDeclaredOnlyComponentsAsync()
|
||||
{
|
||||
@@ -580,6 +722,77 @@ public sealed class PythonLanguageAnalyzerTests
|
||||
return path;
|
||||
}
|
||||
|
||||
private static void CreateWheelpkgWheel(string wheelPath)
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(wheelPath)!);
|
||||
|
||||
var initBytes = Encoding.UTF8.GetBytes("__version__ = \"1.0.0\"\n");
|
||||
var metadataBytes = Encoding.UTF8.GetBytes(
|
||||
$"Metadata-Version: 2.1\nName: wheelpkg\nVersion: 1.0.0\nSummary: Wheel fixture\n{Environment.NewLine}");
|
||||
var wheelBytes = Encoding.UTF8.GetBytes(
|
||||
"Wheel-Version: 1.0\nGenerator: stellaops-test\nRoot-Is-Purelib: true\nTag: py3-none-any\n");
|
||||
|
||||
var recordContent = new StringBuilder()
|
||||
.AppendLine($"wheelpkg/__init__.py,sha256={ComputeSha256Base64(initBytes)},{initBytes.Length}")
|
||||
.AppendLine($"wheelpkg-1.0.0.dist-info/METADATA,sha256={ComputeSha256Base64(metadataBytes)},{metadataBytes.Length}")
|
||||
.AppendLine($"wheelpkg-1.0.0.dist-info/WHEEL,sha256={ComputeSha256Base64(wheelBytes)},{wheelBytes.Length}")
|
||||
.AppendLine("wheelpkg-1.0.0.dist-info/RECORD,,")
|
||||
.ToString();
|
||||
var recordBytes = Encoding.UTF8.GetBytes(recordContent);
|
||||
|
||||
if (File.Exists(wheelPath))
|
||||
{
|
||||
File.Delete(wheelPath);
|
||||
}
|
||||
|
||||
using (var stream = File.Create(wheelPath))
|
||||
using (var archive = new ZipArchive(stream, ZipArchiveMode.Create, leaveOpen: false))
|
||||
{
|
||||
WriteEntry(archive, "wheelpkg/__init__.py", initBytes);
|
||||
WriteEntry(archive, "wheelpkg-1.0.0.dist-info/METADATA", metadataBytes);
|
||||
WriteEntry(archive, "wheelpkg-1.0.0.dist-info/WHEEL", wheelBytes);
|
||||
WriteEntry(archive, "wheelpkg-1.0.0.dist-info/RECORD", recordBytes);
|
||||
}
|
||||
|
||||
static void WriteEntry(ZipArchive archive, string entryName, byte[] content)
|
||||
{
|
||||
var entry = archive.CreateEntry(entryName);
|
||||
entry.LastWriteTime = new DateTimeOffset(2020, 1, 1, 0, 0, 0, TimeSpan.Zero);
|
||||
using var entryStream = entry.Open();
|
||||
entryStream.Write(content, 0, content.Length);
|
||||
}
|
||||
|
||||
static string ComputeSha256Base64(byte[] content)
|
||||
=> Convert.ToBase64String(SHA256.HashData(content));
|
||||
}
|
||||
|
||||
private static void CreateZipappWithEmbeddedRequirements(string zipappPath)
|
||||
{
|
||||
if (File.Exists(zipappPath))
|
||||
{
|
||||
File.Delete(zipappPath);
|
||||
}
|
||||
|
||||
using var fileStream = File.Create(zipappPath);
|
||||
|
||||
var shebangBytes = Encoding.UTF8.GetBytes("#!/usr/bin/python3.11\n");
|
||||
fileStream.Write(shebangBytes);
|
||||
|
||||
using var archive = new ZipArchive(fileStream, ZipArchiveMode.Create, leaveOpen: true);
|
||||
|
||||
WriteTextEntry(archive, "__main__.py", "print('hello')\n");
|
||||
WriteTextEntry(archive, "requirements.txt", "requests==2.28.0\nflask==2.1.0\n");
|
||||
|
||||
static void WriteTextEntry(ZipArchive archive, string name, string content)
|
||||
{
|
||||
var entry = archive.CreateEntry(name, CompressionLevel.NoCompression);
|
||||
entry.LastWriteTime = new DateTimeOffset(2020, 1, 1, 0, 0, 0, TimeSpan.Zero);
|
||||
using var stream = entry.Open();
|
||||
using var writer = new StreamWriter(stream, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
|
||||
writer.Write(content);
|
||||
}
|
||||
}
|
||||
|
||||
// ===== SCAN-PY-405-007 Fixtures =====
|
||||
|
||||
[Fact]
|
||||
|
||||
Reference in New Issue
Block a user