up
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-11-27 23:44:42 +02:00
parent ef6e4b2067
commit 3b96b2e3ea
298 changed files with 47516 additions and 1168 deletions

View File

@@ -4,32 +4,32 @@ using System.Text.Json;
using StellaOps.Scanner.Analyzers.Lang.Python;
using StellaOps.Scanner.Analyzers.Lang.Tests.Harness;
using StellaOps.Scanner.Analyzers.Lang.Tests.TestUtilities;
namespace StellaOps.Scanner.Analyzers.Lang.Python.Tests;
public sealed class PythonLanguageAnalyzerTests
{
[Fact]
namespace StellaOps.Scanner.Analyzers.Lang.Python.Tests;
public sealed class PythonLanguageAnalyzerTests
{
[Fact]
public async Task SimpleVenvFixtureProducesDeterministicOutputAsync()
{
var cancellationToken = TestContext.Current.CancellationToken;
var fixturePath = TestPaths.ResolveFixture("lang", "python", "simple-venv");
var goldenPath = Path.Combine(fixturePath, "expected.json");
var usageHints = new LanguageUsageHints(new[]
{
Path.Combine(fixturePath, "bin", "simple-tool")
});
var analyzers = new ILanguageAnalyzer[]
{
new PythonLanguageAnalyzer()
};
await LanguageAnalyzerTestHarness.AssertDeterministicAsync(
fixturePath,
goldenPath,
analyzers,
var usageHints = new LanguageUsageHints(new[]
{
Path.Combine(fixturePath, "bin", "simple-tool")
});
var analyzers = new ILanguageAnalyzer[]
{
new PythonLanguageAnalyzer()
};
await LanguageAnalyzerTestHarness.AssertDeterministicAsync(
fixturePath,
goldenPath,
analyzers,
cancellationToken,
usageHints);
}
@@ -109,7 +109,7 @@ public sealed class PythonLanguageAnalyzerTests
var json = await LanguageAnalyzerTestHarness.RunToJsonAsync(
fixturePath,
analyzers,
cancellationToken).ConfigureAwait(false);
cancellationToken);
using var document = JsonDocument.Parse(json);
var root = document.RootElement;
@@ -199,6 +199,221 @@ public sealed class PythonLanguageAnalyzerTests
return Convert.ToBase64String(hash);
}
[Fact]
public async Task DetectsSitecustomizeStartupHooksAsync()
{
var cancellationToken = TestContext.Current.CancellationToken;
var fixturePath = CreateTemporaryWorkspace();
try
{
// Create site-packages with sitecustomize.py
var sitePackages = Path.Combine(fixturePath, "lib", "python3.11", "site-packages");
Directory.CreateDirectory(sitePackages);
var sitecustomizePath = Path.Combine(sitePackages, "sitecustomize.py");
await File.WriteAllTextAsync(sitecustomizePath, "# Site customization\nprint('startup hook')", cancellationToken);
// Create a package
await CreatePythonPackageAsync(fixturePath, "test-pkg", "1.0.0", cancellationToken);
var analyzers = new ILanguageAnalyzer[]
{
new PythonLanguageAnalyzer()
};
var json = await LanguageAnalyzerTestHarness.RunToJsonAsync(
fixturePath,
analyzers,
cancellationToken);
using var document = JsonDocument.Parse(json);
var root = document.RootElement;
// Verify startup hooks metadata is present
Assert.True(ComponentHasMetadata(root, "test-pkg", "startupHooks.detected", "true"));
}
finally
{
Directory.Delete(fixturePath, recursive: true);
}
}
[Fact]
public async Task DetectsPthFilesWithImportDirectivesAsync()
{
var cancellationToken = TestContext.Current.CancellationToken;
var fixturePath = CreateTemporaryWorkspace();
try
{
var sitePackages = Path.Combine(fixturePath, "lib", "python3.11", "site-packages");
Directory.CreateDirectory(sitePackages);
// Create a .pth file with import directive
var pthPath = Path.Combine(sitePackages, "test-hooks.pth");
await File.WriteAllTextAsync(pthPath, "import some_module\n/some/path", cancellationToken);
// Create a package
await CreatePythonPackageAsync(fixturePath, "test-pkg", "1.0.0", cancellationToken);
var analyzers = new ILanguageAnalyzer[]
{
new PythonLanguageAnalyzer()
};
var json = await LanguageAnalyzerTestHarness.RunToJsonAsync(
fixturePath,
analyzers,
cancellationToken);
using var document = JsonDocument.Parse(json);
var root = document.RootElement;
// Verify .pth import warning metadata is present
Assert.True(ComponentHasMetadata(root, "test-pkg", "pthFiles.withImports.detected", "true"));
}
finally
{
Directory.Delete(fixturePath, recursive: true);
}
}
[Fact]
public async Task DetectsOciLayerSitePackagesAsync()
{
var cancellationToken = TestContext.Current.CancellationToken;
var fixturePath = CreateTemporaryWorkspace();
try
{
// Create OCI layer structure with packages
var layersDir = Path.Combine(fixturePath, "layers", "layer1", "fs");
var sitePackages = Path.Combine(layersDir, "usr", "lib", "python3.11", "site-packages");
Directory.CreateDirectory(sitePackages);
// Create a package in the layer
var packageDir = Path.Combine(sitePackages, "layered-pkg");
Directory.CreateDirectory(packageDir);
var modulePath = Path.Combine(packageDir, "__init__.py");
await File.WriteAllTextAsync(modulePath, "__version__ = \"1.0.0\"", cancellationToken);
var distInfoDir = Path.Combine(sitePackages, "layered-pkg-1.0.0.dist-info");
Directory.CreateDirectory(distInfoDir);
var metadataPath = Path.Combine(distInfoDir, "METADATA");
await File.WriteAllTextAsync(metadataPath, "Metadata-Version: 2.1\nName: layered-pkg\nVersion: 1.0.0", cancellationToken);
var wheelPath = Path.Combine(distInfoDir, "WHEEL");
await File.WriteAllTextAsync(wheelPath, "Wheel-Version: 1.0", cancellationToken);
var recordPath = Path.Combine(distInfoDir, "RECORD");
await File.WriteAllTextAsync(recordPath, "", cancellationToken);
var analyzers = new ILanguageAnalyzer[]
{
new PythonLanguageAnalyzer()
};
var json = await LanguageAnalyzerTestHarness.RunToJsonAsync(
fixturePath,
analyzers,
cancellationToken);
using var document = JsonDocument.Parse(json);
var root = document.RootElement;
// Verify the package from OCI layers was discovered
var found = false;
foreach (var component in root.EnumerateArray())
{
if (component.TryGetProperty("name", out var nameElement) &&
string.Equals(nameElement.GetString(), "layered-pkg", StringComparison.OrdinalIgnoreCase))
{
found = true;
break;
}
}
Assert.True(found, "Package from OCI layer should be discovered");
}
finally
{
Directory.Delete(fixturePath, recursive: true);
}
}
[Fact]
public async Task DetectsPythonEnvironmentVariablesAsync()
{
var cancellationToken = TestContext.Current.CancellationToken;
var fixturePath = CreateTemporaryWorkspace();
try
{
// Create environment file with PYTHONPATH
var envPath = Path.Combine(fixturePath, ".env");
await File.WriteAllTextAsync(envPath, "PYTHONPATH=/app/lib:/app/vendor", cancellationToken);
// Create a package
await CreatePythonPackageAsync(fixturePath, "test-pkg", "1.0.0", cancellationToken);
var analyzers = new ILanguageAnalyzer[]
{
new PythonLanguageAnalyzer()
};
var json = await LanguageAnalyzerTestHarness.RunToJsonAsync(
fixturePath,
analyzers,
cancellationToken);
using var document = JsonDocument.Parse(json);
var root = document.RootElement;
// Verify PYTHONPATH warning metadata is present
Assert.True(ComponentHasMetadata(root, "test-pkg", "env.pythonpath", "/app/lib:/app/vendor"));
Assert.True(ComponentHasMetadata(root, "test-pkg", "env.pythonpath.warning", "PYTHONPATH is set; may affect module resolution"));
}
finally
{
Directory.Delete(fixturePath, recursive: true);
}
}
[Fact]
public async Task DetectsPyvenvConfigAsync()
{
var cancellationToken = TestContext.Current.CancellationToken;
var fixturePath = CreateTemporaryWorkspace();
try
{
// Create pyvenv.cfg file
var pyvenvPath = Path.Combine(fixturePath, "pyvenv.cfg");
await File.WriteAllTextAsync(pyvenvPath, "home = /usr/local/bin\ninclude-system-site-packages = false", cancellationToken);
// Create a package
await CreatePythonPackageAsync(fixturePath, "test-pkg", "1.0.0", cancellationToken);
var analyzers = new ILanguageAnalyzer[]
{
new PythonLanguageAnalyzer()
};
var json = await LanguageAnalyzerTestHarness.RunToJsonAsync(
fixturePath,
analyzers,
cancellationToken);
using var document = JsonDocument.Parse(json);
var root = document.RootElement;
// Verify PYTHONHOME warning metadata is present (from pyvenv.cfg home)
Assert.True(ComponentHasMetadata(root, "test-pkg", "env.pythonhome", "/usr/local/bin"));
}
finally
{
Directory.Delete(fixturePath, recursive: true);
}
}
private static string CreateTemporaryWorkspace()
{
var path = Path.Combine(Path.GetTempPath(), $"stellaops-python-{Guid.NewGuid():N}");