mirror of
https://github.com/imjasonh/terraform-playground
synced 2026-07-07 23:35:16 +00:00
wheel: conformance test vs uv pip install (oracle) — same site-packages paths + console scripts, no RCE (wheel install runs no code)
Co-authored-by: Jason Hall <imjasonh@users.noreply.github.com>
This commit is contained in:
parent
23503b5534
commit
80ff593fc9
1 changed files with 209 additions and 0 deletions
209
pymage/internal/wheel/conformance_test.go
Normal file
209
pymage/internal/wheel/conformance_test.go
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
package wheel
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/imjasonh/terraform-playground/pymage/internal/ptar"
|
||||
"github.com/imjasonh/terraform-playground/pymage/internal/testwheel"
|
||||
)
|
||||
|
||||
// Installer-added metadata that the oracle (uv/pip) writes but that isn't part
|
||||
// of the wheel payload; excluded from the file-set comparison.
|
||||
var installerExtras = map[string]bool{
|
||||
"RECORD": true, "INSTALLER": true, "REQUESTED": true,
|
||||
"direct_url.json": true, "uv_cache.json": true,
|
||||
}
|
||||
|
||||
// TestConformanceWithPipInstall verifies that pymage places a wheel's files at
|
||||
// the same site-packages-relative paths as a real installer (uv pip / pip),
|
||||
// and that it generates the same console-script launchers.
|
||||
//
|
||||
// This is the key fidelity check: installing a *wheel* runs no project code
|
||||
// (PEP 427 install = unpack + synthesize scripts/metadata), so we can use the
|
||||
// real installer as an oracle without any RCE risk — unlike building an sdist.
|
||||
// Skips when neither uv nor pip is available.
|
||||
func TestConformanceWithPipInstall(t *testing.T) {
|
||||
installer := findInstaller(t)
|
||||
if installer == nil {
|
||||
t.Skip("neither uv nor pip available")
|
||||
}
|
||||
|
||||
dir := t.TempDir()
|
||||
whlPath, _ := testwheel.Write(t, dir, testwheel.Spec{
|
||||
Name: "demolib",
|
||||
Version: "1.2.3",
|
||||
Modules: map[string]string{
|
||||
"demolib/__init__.py": "VERSION = '1.2.3'\n",
|
||||
"demolib/cli.py": "def main():\n print('hi')\n",
|
||||
"demolib/sub/helper.py": "x = 1\n",
|
||||
"demolib/sub/__init__.py": "",
|
||||
},
|
||||
ConsoleScripts: map[string]string{"demo": "demolib.cli:main"},
|
||||
})
|
||||
|
||||
// --- oracle: install the wheel with uv/pip into a target dir ---
|
||||
target := filepath.Join(dir, "oracle")
|
||||
if out, err := installer(whlPath, target); err != nil {
|
||||
t.Skipf("installer failed (treating as environment issue): %v\n%s", err, out)
|
||||
}
|
||||
oracleLib, oracleBin := collectOracle(t, target)
|
||||
|
||||
// --- pymage: compute the install layout from the wheel ---
|
||||
w, err := Open(whlPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() { _ = w.Close() }()
|
||||
files, err := w.Files(layout)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pyLib, pyBin := collectPymage(files)
|
||||
|
||||
// Package payload (non-dist-info) files must match exactly.
|
||||
oraclePayload := nonDistInfo(oracleLib)
|
||||
pyPayload := nonDistInfo(pyLib)
|
||||
if !equalSets(oraclePayload, pyPayload) {
|
||||
t.Errorf("payload file set differs:\n pip-only: %v\n pymage-only: %v",
|
||||
diff(oraclePayload, pyPayload), diff(pyPayload, oraclePayload))
|
||||
}
|
||||
|
||||
// pymage's dist-info files must be a subset of the oracle's (the oracle adds
|
||||
// RECORD/INSTALLER/etc. that we don't synthesize).
|
||||
for f := range distInfoOnly(pyLib) {
|
||||
if !oracleLib[f] {
|
||||
t.Errorf("pymage emitted dist-info file the installer did not: %q", f)
|
||||
}
|
||||
}
|
||||
|
||||
// Both must generate the console-script launcher.
|
||||
if !pyBin["demo"] {
|
||||
t.Errorf("pymage did not generate console script 'demo'; bin=%v", keys(pyBin))
|
||||
}
|
||||
if !oracleBin["demo"] {
|
||||
t.Logf("note: oracle bin did not contain 'demo' (bin=%v) — installer may place scripts elsewhere", keys(oracleBin))
|
||||
}
|
||||
}
|
||||
|
||||
func findInstaller(t *testing.T) func(whl, target string) ([]byte, error) {
|
||||
t.Helper()
|
||||
if _, err := exec.LookPath("uv"); err == nil {
|
||||
return func(whl, target string) ([]byte, error) {
|
||||
cmd := exec.Command("uv", "pip", "install", "--no-deps", "--no-compile", "--target", target, whl)
|
||||
return cmd.CombinedOutput()
|
||||
}
|
||||
}
|
||||
if _, err := exec.LookPath("pip"); err == nil {
|
||||
return func(whl, target string) ([]byte, error) {
|
||||
cmd := exec.Command("pip", "install", "--no-deps", "--no-compile", "--no-index", "--target", target, whl)
|
||||
return cmd.CombinedOutput()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// collectOracle returns the set of site-packages-relative paths under target
|
||||
// (excluding bin and .pyc) and the set of script basenames in target/bin.
|
||||
func collectOracle(t *testing.T, target string) (lib, bin map[string]bool) {
|
||||
t.Helper()
|
||||
lib, bin = map[string]bool{}, map[string]bool{}
|
||||
_ = filepath.WalkDir(target, func(p string, d os.DirEntry, err error) error {
|
||||
if err != nil || d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
rel, _ := filepath.Rel(target, p)
|
||||
rel = filepath.ToSlash(rel)
|
||||
switch {
|
||||
case rel == ".lock" || strings.HasSuffix(rel, ".pyc") || strings.Contains(rel, "__pycache__"):
|
||||
return nil
|
||||
case strings.HasPrefix(rel, "bin/"):
|
||||
bin[strings.TrimPrefix(rel, "bin/")] = true
|
||||
default:
|
||||
if base := filepath.Base(rel); installerExtras[base] {
|
||||
return nil
|
||||
}
|
||||
lib[rel] = true
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return lib, bin
|
||||
}
|
||||
|
||||
// collectPymage maps pymage's ptar paths to site-packages-relative lib paths
|
||||
// and bin script basenames.
|
||||
func collectPymage(files []ptar.File) (lib, bin map[string]bool) {
|
||||
lib, bin = map[string]bool{}, map[string]bool{}
|
||||
site := layout.SitePackages() + "/"
|
||||
bdir := layout.BinDir() + "/"
|
||||
for _, f := range files {
|
||||
p := f.Path
|
||||
switch {
|
||||
case strings.HasPrefix(p, site):
|
||||
rel := strings.TrimPrefix(p, site)
|
||||
if base := filepath.Base(rel); installerExtras[base] {
|
||||
continue
|
||||
}
|
||||
lib[rel] = true
|
||||
case strings.HasPrefix(p, bdir):
|
||||
bin[strings.TrimPrefix(p, bdir)] = true
|
||||
}
|
||||
}
|
||||
return lib, bin
|
||||
}
|
||||
|
||||
func nonDistInfo(m map[string]bool) map[string]bool {
|
||||
out := map[string]bool{}
|
||||
for k := range m {
|
||||
if !strings.Contains(k, ".dist-info/") {
|
||||
out[k] = true
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func distInfoOnly(m map[string]bool) map[string]bool {
|
||||
out := map[string]bool{}
|
||||
for k := range m {
|
||||
if strings.Contains(k, ".dist-info/") {
|
||||
out[k] = true
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func equalSets(a, b map[string]bool) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for k := range a {
|
||||
if !b[k] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func diff(a, b map[string]bool) []string {
|
||||
var out []string
|
||||
for k := range a {
|
||||
if !b[k] {
|
||||
out = append(out, k)
|
||||
}
|
||||
}
|
||||
sort.Strings(out)
|
||||
return out
|
||||
}
|
||||
|
||||
func keys(m map[string]bool) []string {
|
||||
var out []string
|
||||
for k := range m {
|
||||
out = append(out, k)
|
||||
}
|
||||
sort.Strings(out)
|
||||
return out
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue