port: add MSVC600 CPython compatibility layer
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
/* Focused, isolated compile smoke test for Include/internal/pycore_atomic.h
|
||||
* under VC6/_M_IX86.
|
||||
*
|
||||
* Deliberately does NOT go through Python.h or any other pycore_*.h header:
|
||||
* pycore_interp.h (reached transitively from a real core translation unit
|
||||
* like Modules/main.c) additionally requires <stdbool.h>, a separate,
|
||||
* unrelated VC6 gap tracked in PORT_STATUS.md. Isolating pycore_atomic.h
|
||||
* here keeps this probe's result attributable to the atomics compatibility
|
||||
* work specifically.
|
||||
*
|
||||
* Py_BUILD_CORE is supplied by the probe script (/D Py_BUILD_CORE), the
|
||||
* same way it is for a real core translation unit. <inttypes.h> (resolved
|
||||
* to compat/msvc600/inttypes.h for VC6 via the include path) provides
|
||||
* uintptr_t, which pycore_atomic.h needs but does not itself include; in
|
||||
* a real build this comes from Python.h/pyport.h, included ahead of any
|
||||
* pycore_*.h header.
|
||||
*/
|
||||
#include <inttypes.h>
|
||||
#include "pycore_atomic.h"
|
||||
|
||||
static _Py_atomic_int atomic_int_value;
|
||||
static _Py_atomic_address atomic_addr_value;
|
||||
|
||||
int
|
||||
vc6_atomic_smoke(void)
|
||||
{
|
||||
_Py_atomic_store(&atomic_int_value, 1);
|
||||
_Py_atomic_store_relaxed(&atomic_int_value, 2);
|
||||
_Py_atomic_store(&atomic_addr_value, (uintptr_t)0);
|
||||
_Py_atomic_store_relaxed(&atomic_addr_value, (uintptr_t)1);
|
||||
|
||||
return _Py_atomic_load(&atomic_int_value)
|
||||
+ _Py_atomic_load_relaxed(&atomic_int_value)
|
||||
+ (int)_Py_atomic_load(&atomic_addr_value)
|
||||
+ (int)_Py_atomic_load_relaxed(&atomic_addr_value);
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/* Focused compile smoke test for the VC6 C99 compound-literal /
|
||||
* designated-initializer fixes applied to cpython/Modules/main.c.
|
||||
*
|
||||
* VC6 (a pre-C99 compiler) supports neither C99 compound literals
|
||||
* (`(PyStatus){...}`) nor C99 designated initializers (`{ .field = value,
|
||||
* ... }`), at any position in a block. Modules/main.c relied on both via
|
||||
* three distinct patterns:
|
||||
*
|
||||
* 1. `status = _PyStatus_OK();` -- a compound-literal macro used as a
|
||||
* plain assignment's right-hand side. Fixed by calling the existing
|
||||
* public API function PyStatus_Ok() instead, which
|
||||
* Python/initconfig.c defines as `return _PyStatus_OK();` -- the
|
||||
* identical value, no header change needed.
|
||||
* 2. `cf = _PyCompilerFlags_INIT;` -- likewise a compound-literal macro
|
||||
* used as a plain assignment. Fixed by assigning PyCompilerFlags's
|
||||
* two fields explicitly (cf_flags, cf_feature_version), matching the
|
||||
* macro's own definition field-for-field.
|
||||
* 3. `_PyArgv args = { .argc = argc, ... };` -- a designated-initializer
|
||||
* aggregate initializer with no macro involved. Fixed by declaring
|
||||
* the variable bare and assigning each field explicitly; field order
|
||||
* does not matter for plain assignment, unlike an initializer list.
|
||||
*
|
||||
* See PORT_STATUS.md ("Compound-literal/designated-initializer slice") for
|
||||
* full detail. Like vc6_mixed_decls_smoke.c, this fixture includes the
|
||||
* real "Python.h" (Py_BUILD_CORE is supplied by the probe script) because
|
||||
* PyStatus, PyCompilerFlags, and _PyArgv are real CPython types pulled in
|
||||
* through the normal public/internal header chain, the same way
|
||||
* Modules/main.c itself reaches them.
|
||||
*/
|
||||
#include "Python.h"
|
||||
#include "pycore_initconfig.h" // _PyArgv, _PyStatus_OK()
|
||||
|
||||
PyStatus
|
||||
vc6_status_ok_smoke(void)
|
||||
{
|
||||
PyStatus status;
|
||||
|
||||
/* pattern 1: was `status = _PyStatus_OK();` */
|
||||
status = PyStatus_Ok();
|
||||
return status;
|
||||
}
|
||||
|
||||
PyCompilerFlags
|
||||
vc6_compiler_flags_smoke(void)
|
||||
{
|
||||
PyCompilerFlags cf;
|
||||
|
||||
/* pattern 2: was `cf = _PyCompilerFlags_INIT;` */
|
||||
cf.cf_flags = 0;
|
||||
cf.cf_feature_version = PY_MINOR_VERSION;
|
||||
return cf;
|
||||
}
|
||||
|
||||
Py_ssize_t
|
||||
vc6_argv_designated_init_smoke(int argc, wchar_t **argv)
|
||||
{
|
||||
/* pattern 3: was `_PyArgv args = { .argc = argc, ... };` */
|
||||
_PyArgv args;
|
||||
args.argc = argc;
|
||||
args.use_bytes_argv = 0;
|
||||
args.bytes_argv = NULL;
|
||||
args.wchar_argv = argv;
|
||||
return args.argc;
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
/* Focused compile smoke test for the VC6 C89-mixed-declarations fixes
|
||||
* applied to cpython/Modules/main.c.
|
||||
*
|
||||
* VC6 is a C89-only compiler (plus Microsoft extensions): block-scope
|
||||
* declarations must precede all statements in that block. Modules/main.c
|
||||
* declared many locals mid-function (typically right before first use,
|
||||
* C99 style); the fix hoists each declaration to the top of its enclosing
|
||||
* block, without initializer, and turns the original `TYPE name = expr;`
|
||||
* into a plain `name = expr;` assignment at its original position. This
|
||||
* changes no evaluation order and no semantics. See PORT_STATUS.md
|
||||
* ("Modules/main.c mixed-declarations slice") for full detail.
|
||||
*
|
||||
* This fixture is deliberately self-contained (no CPython headers) so it
|
||||
* exercises the *shape* of every hoist pattern used in main.c, independent
|
||||
* of main.c's own evolution:
|
||||
*
|
||||
* 1. a bare declaration hoisted above an early-return statement
|
||||
* (pymain_init's `preconfig`/`config`);
|
||||
* 2. a declaration whose initializer is a side-effecting call, hoisted
|
||||
* bare and assigned at its original site so the call itself still
|
||||
* happens after the same preceding statements
|
||||
* (pymain_import_readline's `mod`, pymain_run_file_obj's `fp`);
|
||||
* 3. two declarations in a row where hoisting the first turns its site
|
||||
* into an assignment statement, which would otherwise make the
|
||||
* second declaration (immediately following) a new violation too
|
||||
* (pymain_run_file_obj's `cf`/`run`);
|
||||
* 4. a declaration inside a nested block (its own scope), after
|
||||
* statements already executed in that block (pymain_run_stdin's
|
||||
* nested `exitcode`);
|
||||
* 5. a platform-conditional declaration with the same name but a
|
||||
* different type per branch, hoisted under the same #ifdef structure
|
||||
* at the top of the function (pymain_run_startup's `env`).
|
||||
*/
|
||||
|
||||
#define TARGET_WINDOWS 1
|
||||
|
||||
/* --- pattern 1: bare declaration hoisted above an early return --- */
|
||||
static int
|
||||
decl_after_early_return(int flag)
|
||||
{
|
||||
int status;
|
||||
int extra;
|
||||
|
||||
status = flag ? 0 : -1;
|
||||
if (status != 0) {
|
||||
return status;
|
||||
}
|
||||
|
||||
extra = 7;
|
||||
return extra;
|
||||
}
|
||||
|
||||
/* --- pattern 2: side-effecting-initializer declaration hoisted bare,
|
||||
* assigned at its original position so the call still happens after the
|
||||
* same preceding statements --- */
|
||||
static int
|
||||
side_effect(int *counter)
|
||||
{
|
||||
(*counter)++;
|
||||
return *counter;
|
||||
}
|
||||
|
||||
static int
|
||||
decl_with_side_effecting_init(int *counter)
|
||||
{
|
||||
int guard;
|
||||
int value;
|
||||
|
||||
guard = (*counter < 0);
|
||||
if (guard) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
value = side_effect(counter);
|
||||
return value;
|
||||
}
|
||||
|
||||
/* --- pattern 3: two declarations in a row, first one hoisted becomes an
|
||||
* assignment, so the second (immediately following) must hoist too --- */
|
||||
static int
|
||||
decl_pair_after_hoist(int *counter)
|
||||
{
|
||||
int guard;
|
||||
int a;
|
||||
int b;
|
||||
|
||||
guard = (*counter < 0);
|
||||
if (guard) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
a = side_effect(counter);
|
||||
b = side_effect(counter);
|
||||
return a + b;
|
||||
}
|
||||
|
||||
/* --- pattern 4: nested-block declaration after statements already
|
||||
* executed within that same nested block --- */
|
||||
static int
|
||||
decl_after_statement_in_nested_block(int interactive)
|
||||
{
|
||||
int outer_result = 0;
|
||||
|
||||
if (interactive) {
|
||||
int inner_value;
|
||||
|
||||
outer_result += 1;
|
||||
outer_result += 2;
|
||||
|
||||
inner_value = outer_result * 10;
|
||||
return inner_value;
|
||||
}
|
||||
|
||||
return outer_result;
|
||||
}
|
||||
|
||||
/* --- pattern 5: platform-conditional declaration, same name, different
|
||||
* type per branch, hoisted under the same #ifdef at the top --- */
|
||||
static long
|
||||
decl_platform_conditional(void)
|
||||
{
|
||||
int ret;
|
||||
#ifdef TARGET_WINDOWS
|
||||
const char *env;
|
||||
#else
|
||||
const long *env;
|
||||
#endif
|
||||
|
||||
ret = 1;
|
||||
if (!ret) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef TARGET_WINDOWS
|
||||
env = "PYTHONSTARTUP";
|
||||
if (env == 0) {
|
||||
return 0;
|
||||
}
|
||||
return (long)env[0];
|
||||
#else
|
||||
env = 0;
|
||||
if (env == 0) {
|
||||
return 0;
|
||||
}
|
||||
return *env;
|
||||
#endif
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
int counter = 0;
|
||||
int total = 0;
|
||||
|
||||
total += decl_after_early_return(1);
|
||||
total += decl_with_side_effecting_init(&counter);
|
||||
total += decl_pair_after_hoist(&counter);
|
||||
total += decl_after_statement_in_nested_block(1);
|
||||
total += (int)decl_platform_conditional();
|
||||
|
||||
return (total >= 0) ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/* Focused compile smoke test for the VC6 C89-mixed-declarations fixes in
|
||||
* four Include/internal/pycore_*.h headers: pycore_code.h, pycore_dict.h,
|
||||
* pycore_list.h, and pycore_call.h.
|
||||
*
|
||||
* VC6 is a C89-only compiler (plus Microsoft extensions): block-scope
|
||||
* declarations must precede all statements in that block. CPython 3.11's
|
||||
* source freely uses C99-style declarations after statements (e.g. after
|
||||
* an assert()); the fix hoists each such declaration to the top of its
|
||||
* enclosing block without changing evaluation order or behavior. See
|
||||
* PORT_STATUS.md ("Mixed-declarations header slice") for full detail.
|
||||
*
|
||||
* Unlike scripts/fixtures/vc6_atomic_smoke.c and vc6_stdbool_smoke.c, this
|
||||
* fixture includes the real "Python.h" (Py_BUILD_CORE is supplied by the
|
||||
* probe script) rather than isolating the pycore_*.h headers from it:
|
||||
* these four headers assume types (PyObject, PyCodeObject, PyListObject,
|
||||
* PyDictValues, vectorcallfunc, ...) that come from the normal public
|
||||
* header chain, the same way real CPython .c sources reach them.
|
||||
*/
|
||||
#include "Python.h"
|
||||
#include "pycore_code.h"
|
||||
#include "pycore_dict.h"
|
||||
#include "pycore_list.h"
|
||||
#include "pycore_call.h"
|
||||
|
||||
int
|
||||
vc6_locals_kind_smoke(PyObject *kinds, int i, _PyLocals_Kind kind)
|
||||
{
|
||||
_PyLocals_SetKind(kinds, i, kind);
|
||||
return (int)_PyLocals_GetKind(kinds, i);
|
||||
}
|
||||
|
||||
uint16_t
|
||||
vc6_adaptive_counter_smoke(uint16_t counter)
|
||||
{
|
||||
return adaptive_counter_backoff(counter);
|
||||
}
|
||||
|
||||
void
|
||||
vc6_dict_values_smoke(PyDictValues *values, Py_ssize_t ix)
|
||||
{
|
||||
_PyDictValues_AddToInsertionOrder(values, ix);
|
||||
}
|
||||
|
||||
int
|
||||
vc6_list_append_smoke(PyListObject *self, PyObject *newitem)
|
||||
{
|
||||
return _PyList_AppendTakeRef(self, newitem);
|
||||
}
|
||||
|
||||
vectorcallfunc
|
||||
vc6_vectorcall_smoke(PyObject *callable)
|
||||
{
|
||||
return _PyVectorcall_FunctionInline(callable);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/* Focused, isolated compile smoke test for compat/msvc600/stdbool.h under
|
||||
* VC6.
|
||||
*
|
||||
* Deliberately does NOT go through pycore_interp.h or any other pycore_*.h
|
||||
* header: this isolates the result to the <stdbool.h> shim itself, the
|
||||
* same way scripts/fixtures/vc6_atomic_smoke.c isolates pycore_atomic.h.
|
||||
* Modules/main.c (see scripts/vc6-probe-core.test.sh) is the real-header
|
||||
* reference case that proves compat/msvc600/stdbool.h is actually what
|
||||
* pycore_interp.h's `#include <stdbool.h>` resolves to via the VC6 probe
|
||||
* scripts' include path.
|
||||
*/
|
||||
#include <stdbool.h>
|
||||
|
||||
static bool vc6_stdbool_flag = true;
|
||||
|
||||
int
|
||||
vc6_stdbool_smoke(void)
|
||||
{
|
||||
bool a = true;
|
||||
bool b = false;
|
||||
bool c = a && !b;
|
||||
|
||||
vc6_stdbool_flag = c;
|
||||
|
||||
if (!__bool_true_false_are_defined) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return (int)(a == true) + (int)(b == false) + (int)(c == true)
|
||||
+ (int)vc6_stdbool_flag;
|
||||
}
|
||||
Executable
+104
@@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Py_BUILD_CORE compile probe for the Windows 9x port.
|
||||
#
|
||||
# scripts/vc6-probe.sh only ever compiles a public-header translation unit
|
||||
# (Programs/python.c). CPython's internal (`Include/internal/pycore_*.h`)
|
||||
# headers are a different, stricter surface: every one of them requires
|
||||
# Py_BUILD_CORE to be defined and Include/internal on the include path, and
|
||||
# they use compiler features the public headers avoid. This script probes
|
||||
# that surface directly by compiling Modules/main.c, the first internal
|
||||
# translation unit CPython's own build reaches, the same way
|
||||
# scripts/vc6-probe.sh probes the public one: run the external MSVC600
|
||||
# `cl.exe` under Wine against the CPython 3.11.16 baseline in ./cpython,
|
||||
# using the project-local compatibility headers in ./compat/msvc600.
|
||||
# Compile-only (/c): this measures how far the VC6 front end gets, it does
|
||||
# not attempt to link.
|
||||
#
|
||||
# The MSVC600 bundle is an external, local-only prerequisite; nothing under
|
||||
# MSVC600/ is part of this project's source.
|
||||
#
|
||||
# Usage:
|
||||
# scripts/vc6-probe-core.sh # probe Modules/main.c
|
||||
# scripts/vc6-probe-core.sh path/to/other.c # probe another core TU
|
||||
#
|
||||
# Environment:
|
||||
# MSVC600_ROOT override the MSVC600 location (default: <repo>/MSVC600)
|
||||
# WINEPREFIX override the Wine prefix (default: ~/.wine-win9xport)
|
||||
|
||||
set -u
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
|
||||
# Reuse winpath()/strip_prefix() from vc6-probe.sh instead of duplicating
|
||||
# them; sourcing it does not run the probe itself (see its own guard).
|
||||
# shellcheck source=scripts/vc6-probe.sh
|
||||
source "$REPO_ROOT/scripts/vc6-probe.sh"
|
||||
|
||||
run_probe_core() {
|
||||
MSVC600_ROOT="${MSVC600_ROOT:-$REPO_ROOT/MSVC600}"
|
||||
BUILD_DIR="$REPO_ROOT/build/vc6-probe-core"
|
||||
|
||||
SOURCE_REL="${1:-cpython/Modules/main.c}"
|
||||
SOURCE_ABS="$REPO_ROOT/$SOURCE_REL"
|
||||
|
||||
CL="$MSVC600_ROOT/VC98/Bin/CL.EXE"
|
||||
VCVARS="$MSVC600_ROOT/VC98/Bin/VCVARS32.BAT"
|
||||
|
||||
for required in "$CL" "$VCVARS" "$MSVC600_ROOT/VC98/Include" \
|
||||
"$MSVC600_ROOT/Common/MSDev98/Bin" "$SOURCE_ABS"; do
|
||||
if [ ! -e "$required" ]; then
|
||||
echo "vc6-probe-core: missing prerequisite: $required" >&2
|
||||
return 2
|
||||
fi
|
||||
done
|
||||
|
||||
if ! command -v wine >/dev/null 2>&1; then
|
||||
echo "vc6-probe-core: wine is not installed" >&2
|
||||
return 2
|
||||
fi
|
||||
|
||||
export WINEPREFIX="${WINEPREFIX:-$HOME/.wine-win9xport}"
|
||||
export WINEDEBUG="${WINEDEBUG:--all}"
|
||||
|
||||
# CL.EXE loads C1/C2 from VC98/Bin and MSPDB60.DLL from Common/MSDev98/Bin.
|
||||
export WINEPATH="$(winpath "$MSVC600_ROOT/VC98/Bin");$(winpath "$MSVC600_ROOT/Common/MSDev98/Bin")"
|
||||
|
||||
mkdir -p "$BUILD_DIR"
|
||||
|
||||
# Include/internal must come before Include so pycore_*.h headers can
|
||||
# find each other with their own bare #include "pycore_foo.h" style,
|
||||
# same order CPython's own PC build uses.
|
||||
INCLUDE_ARGS=(
|
||||
"$(winpath "$REPO_ROOT/compat/msvc600")"
|
||||
"$(winpath "$REPO_ROOT/cpython/Include/internal")"
|
||||
"$(winpath "$REPO_ROOT/cpython/Include")"
|
||||
"$(winpath "$REPO_ROOT/cpython/PC")"
|
||||
"$(winpath "$MSVC600_ROOT/VC98/Include")"
|
||||
)
|
||||
|
||||
echo "== MSVC600 Py_BUILD_CORE compile probe =="
|
||||
echo "source : $SOURCE_REL"
|
||||
echo "prefix : $WINEPREFIX"
|
||||
echo
|
||||
|
||||
CMD="call $(winpath "$VCVARS") && cd /d $(winpath "$REPO_ROOT/cpython") && cl /nologo /D Py_BUILD_CORE"
|
||||
for include_arg in "${INCLUDE_ARGS[@]}"; do
|
||||
CMD+=" /I $include_arg"
|
||||
done
|
||||
CMD+=" /c $(winpath "$SOURCE_ABS") /Fo$(winpath "$BUILD_DIR")\\"
|
||||
|
||||
wine cmd /c "$CMD" 2>&1 |
|
||||
grep -v '^[0-9a-f]\{4\}:err:' |
|
||||
strip_prefix "$(winpath "$REPO_ROOT")\\"
|
||||
|
||||
status="${PIPESTATUS[0]}"
|
||||
echo
|
||||
echo "== cl.exe exit status: $status =="
|
||||
return "$status"
|
||||
}
|
||||
|
||||
if [ "${BASH_SOURCE[0]}" = "${0}" ]; then
|
||||
run_probe_core "$@"
|
||||
exit "$?"
|
||||
fi
|
||||
Executable
+337
@@ -0,0 +1,337 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Regression tests for scripts/vc6-probe-core.sh and, through it, the VC6
|
||||
# _Py_ATOMIC_VC6_X86 branch added to
|
||||
# cpython/Include/internal/pycore_atomic.h.
|
||||
#
|
||||
# Skipped (not failed) when wine + MSVC600 are unavailable, matching
|
||||
# scripts/vc6-probe.test.sh's convention, since CI/dev boxes without the
|
||||
# external MSVC600 bundle cannot exercise this.
|
||||
#
|
||||
# Usage: scripts/vc6-probe-core.test.sh
|
||||
|
||||
set -u
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
PROBE="$REPO_ROOT/scripts/vc6-probe-core.sh"
|
||||
|
||||
fail=0
|
||||
pass() { echo "ok - $1"; }
|
||||
bad() { echo "not ok - $1"; fail=1; }
|
||||
|
||||
MSVC600_ROOT="${MSVC600_ROOT:-$REPO_ROOT/MSVC600}"
|
||||
if command -v wine >/dev/null 2>&1 && [ -e "$MSVC600_ROOT/VC98/Bin/CL.EXE" ]; then
|
||||
|
||||
# --- positive case: the isolated atomics smoke fixture compiles clean ---
|
||||
out="$("$PROBE" scripts/fixtures/vc6_atomic_smoke.c 2>&1)"
|
||||
status=$?
|
||||
|
||||
if [ "$status" -eq 0 ]; then
|
||||
pass "pycore_atomic.h VC6 branch: atomics smoke fixture compiles (exit 0)"
|
||||
else
|
||||
bad "pycore_atomic.h VC6 branch: atomics smoke fixture compiles (exit $status)"
|
||||
echo "$out"
|
||||
fi
|
||||
|
||||
if printf '%s' "$out" | grep -qi 'error'; then
|
||||
bad "atomics smoke fixture: no compiler errors in output"
|
||||
else
|
||||
pass "atomics smoke fixture: no compiler errors in output"
|
||||
fi
|
||||
|
||||
if printf '%s' "$out" | grep -q 'intrin.h'; then
|
||||
bad "atomics smoke fixture: does not hit the intrin.h blocker"
|
||||
else
|
||||
pass "atomics smoke fixture: does not hit the intrin.h blocker"
|
||||
fi
|
||||
|
||||
# --- negative case: a deliberately invalid atomics usage still fails ---
|
||||
BAD_SRC="$REPO_ROOT/scripts/fixtures/_vc6_atomic_smoke_bad.c"
|
||||
cat > "$BAD_SRC" <<'EOF'
|
||||
#include <inttypes.h>
|
||||
#include "pycore_atomic.h"
|
||||
int bad(void) { return this_identifier_does_not_exist; }
|
||||
EOF
|
||||
trap 'rm -f "$BAD_SRC"' EXIT
|
||||
|
||||
out_bad="$("$PROBE" scripts/fixtures/_vc6_atomic_smoke_bad.c 2>&1)"
|
||||
status_bad=$?
|
||||
|
||||
if [ "$status_bad" -ne 0 ]; then
|
||||
pass "probe-core reports nonzero exit for a broken translation unit"
|
||||
else
|
||||
bad "probe-core reports nonzero exit for a broken translation unit (got: $status_bad)"
|
||||
fi
|
||||
|
||||
if printf '%s' "$out_bad" | grep -q 'error C'; then
|
||||
pass "probe-core output surfaces the compiler diagnostic"
|
||||
else
|
||||
bad "probe-core output surfaces the compiler diagnostic"
|
||||
fi
|
||||
|
||||
# --- reference case: Modules/main.c reaches past pycore_atomic.h ---
|
||||
# main.c is expected to fail today (pycore_interp.h needs <stdbool.h>,
|
||||
# a separate, unrelated VC6 gap) but must not fail *at*
|
||||
# pycore_atomic.h/intrin.h; that would mean the atomics branch
|
||||
# regressed even though the isolated fixture above passed.
|
||||
out_main="$("$PROBE" cpython/Modules/main.c 2>&1)"
|
||||
if printf '%s' "$out_main" | grep -q 'pycore_atomic.h.*intrin.h\|intrin.h.*No such file'; then
|
||||
bad "Modules/main.c probe: does not regress to the pycore_atomic.h/intrin.h blocker"
|
||||
else
|
||||
pass "Modules/main.c probe: does not regress to the pycore_atomic.h/intrin.h blocker"
|
||||
fi
|
||||
|
||||
# --- positive case: the isolated stdbool smoke fixture compiles clean ---
|
||||
out_bool="$("$PROBE" scripts/fixtures/vc6_stdbool_smoke.c 2>&1)"
|
||||
status_bool=$?
|
||||
|
||||
if [ "$status_bool" -eq 0 ]; then
|
||||
pass "compat/msvc600/stdbool.h: stdbool smoke fixture compiles (exit 0)"
|
||||
else
|
||||
bad "compat/msvc600/stdbool.h: stdbool smoke fixture compiles (exit $status_bool)"
|
||||
echo "$out_bool"
|
||||
fi
|
||||
|
||||
if printf '%s' "$out_bool" | grep -qi 'error'; then
|
||||
bad "stdbool smoke fixture: no compiler errors in output"
|
||||
else
|
||||
pass "stdbool smoke fixture: no compiler errors in output"
|
||||
fi
|
||||
|
||||
# --- negative case: a deliberately invalid bool usage still fails ---
|
||||
BAD_BOOL_SRC="$REPO_ROOT/scripts/fixtures/_vc6_stdbool_smoke_bad.c"
|
||||
cat > "$BAD_BOOL_SRC" <<'EOF'
|
||||
#include <stdbool.h>
|
||||
int bad(void) { return this_identifier_does_not_exist; }
|
||||
EOF
|
||||
trap 'rm -f "$BAD_SRC" "$BAD_BOOL_SRC"' EXIT
|
||||
|
||||
out_bool_bad="$("$PROBE" scripts/fixtures/_vc6_stdbool_smoke_bad.c 2>&1)"
|
||||
status_bool_bad=$?
|
||||
|
||||
if [ "$status_bool_bad" -ne 0 ]; then
|
||||
pass "probe-core reports nonzero exit for a broken stdbool translation unit"
|
||||
else
|
||||
bad "probe-core reports nonzero exit for a broken stdbool translation unit (got: $status_bool_bad)"
|
||||
fi
|
||||
|
||||
if printf '%s' "$out_bool_bad" | grep -q 'error C'; then
|
||||
pass "probe-core output surfaces the compiler diagnostic (stdbool case)"
|
||||
else
|
||||
bad "probe-core output surfaces the compiler diagnostic (stdbool case)"
|
||||
fi
|
||||
|
||||
# --- reference case: Modules/main.c reaches past pycore_interp.h's
|
||||
# <stdbool.h> include. It is expected to still fail today -- the next,
|
||||
# unrelated blocker is VC6's C89-only parser rejecting C99-style mixed
|
||||
# declarations-and-code in several pycore_*.h inline functions (e.g.
|
||||
# Include/internal/pycore_code.h's _PyLocals_GetKind(), which declares
|
||||
# `char *ptr` after a preceding `assert(...)` statement) -- but it must
|
||||
# not fail *at* <stdbool.h> anymore; that would mean this slice
|
||||
# regressed even though the isolated fixture above passed.
|
||||
if printf '%s' "$out_main" | grep -q "Cannot open include file: 'stdbool.h'"; then
|
||||
bad "Modules/main.c probe: does not regress to the pycore_interp.h/stdbool.h blocker"
|
||||
else
|
||||
pass "Modules/main.c probe: does not regress to the pycore_interp.h/stdbool.h blocker"
|
||||
fi
|
||||
|
||||
# --- positive case: the isolated mixed-declarations smoke fixture
|
||||
# compiles clean (pycore_code.h, pycore_dict.h, pycore_list.h,
|
||||
# pycore_call.h all hoisted their C99 mid-block declarations) ---
|
||||
out_decls="$("$PROBE" scripts/fixtures/vc6_mixed_decls_smoke.c 2>&1)"
|
||||
status_decls=$?
|
||||
|
||||
if [ "$status_decls" -eq 0 ]; then
|
||||
pass "mixed-declarations header slice: smoke fixture compiles (exit 0)"
|
||||
else
|
||||
bad "mixed-declarations header slice: smoke fixture compiles (exit $status_decls)"
|
||||
echo "$out_decls"
|
||||
fi
|
||||
|
||||
if printf '%s' "$out_decls" | grep -qi 'error'; then
|
||||
bad "mixed-declarations smoke fixture: no compiler errors in output"
|
||||
else
|
||||
pass "mixed-declarations smoke fixture: no compiler errors in output"
|
||||
fi
|
||||
|
||||
# --- negative case: a deliberately invalid mixed-declarations usage
|
||||
# still fails ---
|
||||
BAD_DECLS_SRC="$REPO_ROOT/scripts/fixtures/_vc6_mixed_decls_smoke_bad.c"
|
||||
cat > "$BAD_DECLS_SRC" <<'EOF'
|
||||
#include "Python.h"
|
||||
#include "pycore_code.h"
|
||||
int bad(void) { return this_identifier_does_not_exist; }
|
||||
EOF
|
||||
trap 'rm -f "$BAD_SRC" "$BAD_BOOL_SRC" "$BAD_DECLS_SRC"' EXIT
|
||||
|
||||
out_decls_bad="$("$PROBE" scripts/fixtures/_vc6_mixed_decls_smoke_bad.c 2>&1)"
|
||||
status_decls_bad=$?
|
||||
|
||||
if [ "$status_decls_bad" -ne 0 ]; then
|
||||
pass "probe-core reports nonzero exit for a broken mixed-declarations translation unit"
|
||||
else
|
||||
bad "probe-core reports nonzero exit for a broken mixed-declarations translation unit (got: $status_decls_bad)"
|
||||
fi
|
||||
|
||||
if printf '%s' "$out_decls_bad" | grep -q 'error C'; then
|
||||
pass "probe-core output surfaces the compiler diagnostic (mixed-declarations case)"
|
||||
else
|
||||
bad "probe-core output surfaces the compiler diagnostic (mixed-declarations case)"
|
||||
fi
|
||||
|
||||
# --- reference case: Modules/main.c no longer fails inside any of the
|
||||
# four fixed pycore_*.h headers. It is expected to still fail today --
|
||||
# Modules/main.c itself uses the same C99 mixed-declarations pattern
|
||||
# pervasively in its own function bodies, a much larger, whole-file
|
||||
# source rewrite that is out of scope for this header-only slice; see
|
||||
# PORT_STATUS.md's "Mixed-declarations header slice" section for the
|
||||
# documented boundary -- but it must not fail *inside*
|
||||
# pycore_code.h/pycore_dict.h/pycore_list.h/pycore_call.h anymore;
|
||||
# that would mean this slice regressed even though the isolated
|
||||
# fixture above passed.
|
||||
if printf '%s' "$out_main" | grep -qE 'pycore_(code|dict|list|call)\.h\([0-9]+\)'; then
|
||||
bad "Modules/main.c probe: does not regress to the pycore_code/dict/list/call.h mixed-declarations blocker"
|
||||
else
|
||||
pass "Modules/main.c probe: does not regress to the pycore_code/dict/list/call.h mixed-declarations blocker"
|
||||
fi
|
||||
|
||||
# --- positive case: the isolated declaration-order smoke fixture
|
||||
# compiles clean. It exercises, self-contained, the same five hoist
|
||||
# shapes applied to Modules/main.c itself (bare hoist above an early
|
||||
# return, side-effecting-initializer hoist, a hoisted pair where fixing
|
||||
# the first declaration would otherwise turn the second into a new
|
||||
# violation, a nested-block hoist, and a platform-conditional same-name
|
||||
# hoist) so this guards the general transformation, independent of
|
||||
# main.c's own future edits. ---
|
||||
out_decl_order="$("$PROBE" scripts/fixtures/vc6_decl_order_smoke.c 2>&1)"
|
||||
status_decl_order=$?
|
||||
|
||||
if [ "$status_decl_order" -eq 0 ]; then
|
||||
pass "Modules/main.c declaration-order slice: smoke fixture compiles (exit 0)"
|
||||
else
|
||||
bad "Modules/main.c declaration-order slice: smoke fixture compiles (exit $status_decl_order)"
|
||||
echo "$out_decl_order"
|
||||
fi
|
||||
|
||||
if printf '%s' "$out_decl_order" | grep -qi 'error'; then
|
||||
bad "declaration-order smoke fixture: no compiler errors in output"
|
||||
else
|
||||
pass "declaration-order smoke fixture: no compiler errors in output"
|
||||
fi
|
||||
|
||||
# --- negative case: a deliberately invalid declaration-order usage
|
||||
# still fails ---
|
||||
BAD_DECL_ORDER_SRC="$REPO_ROOT/scripts/fixtures/_vc6_decl_order_smoke_bad.c"
|
||||
cat > "$BAD_DECL_ORDER_SRC" <<'EOF'
|
||||
int bad(void) { return this_identifier_does_not_exist; }
|
||||
EOF
|
||||
trap 'rm -f "$BAD_SRC" "$BAD_BOOL_SRC" "$BAD_DECLS_SRC" "$BAD_DECL_ORDER_SRC"' EXIT
|
||||
|
||||
out_decl_order_bad="$("$PROBE" scripts/fixtures/_vc6_decl_order_smoke_bad.c 2>&1)"
|
||||
status_decl_order_bad=$?
|
||||
|
||||
if [ "$status_decl_order_bad" -ne 0 ]; then
|
||||
pass "probe-core reports nonzero exit for a broken declaration-order translation unit"
|
||||
else
|
||||
bad "probe-core reports nonzero exit for a broken declaration-order translation unit (got: $status_decl_order_bad)"
|
||||
fi
|
||||
|
||||
if printf '%s' "$out_decl_order_bad" | grep -q 'error C'; then
|
||||
pass "probe-core output surfaces the compiler diagnostic (declaration-order case)"
|
||||
else
|
||||
bad "probe-core output surfaces the compiler diagnostic (declaration-order case)"
|
||||
fi
|
||||
|
||||
# --- reference case: Modules/main.c no longer fails on any C89
|
||||
# mixed-declaration site anywhere in its own body (the C2146/C2065/
|
||||
# C2275 "illegal use of this type as an expression" pattern tied to a
|
||||
# declaration keyword). At the time this check was written, main.c
|
||||
# still failed with a distinct, out-of-scope blocker instead (C99
|
||||
# compound literals/designated initializers, see below); that blocker
|
||||
# is now also fixed (see the compound-literal checks further down),
|
||||
# but this check's job is narrower and unchanged: main.c must not show
|
||||
# the declaration-order-specific error codes; that would mean this
|
||||
# slice regressed even though the isolated fixture above passed.
|
||||
if printf '%s' "$out_main" | grep -qE "main\.c\([0-9]+\) : error C2146|main\.c\([0-9]+\) : error C2065.*undeclared identifier|main\.c\([0-9]+\) : error C2275"; then
|
||||
bad "Modules/main.c probe: does not regress to a C89 mixed-declaration error inside main.c's own body"
|
||||
else
|
||||
pass "Modules/main.c probe: does not regress to a C89 mixed-declaration error inside main.c's own body"
|
||||
fi
|
||||
|
||||
# --- positive case: the isolated compound-literal/designated-
|
||||
# initializer smoke fixture compiles clean. It exercises, self-
|
||||
# contained (aside from the real Python.h/pycore_initconfig.h types
|
||||
# it needs), the three replacement patterns applied to main.c: calling
|
||||
# PyStatus_Ok() instead of the _PyStatus_OK() compound-literal macro,
|
||||
# explicit field assignment instead of the _PyCompilerFlags_INIT
|
||||
# compound-literal macro, and explicit field assignment instead of a
|
||||
# _PyArgv designated-initializer aggregate. ---
|
||||
out_compound="$("$PROBE" scripts/fixtures/vc6_compound_literal_smoke.c 2>&1)"
|
||||
status_compound=$?
|
||||
|
||||
if [ "$status_compound" -eq 0 ]; then
|
||||
pass "compound-literal slice: smoke fixture compiles (exit 0)"
|
||||
else
|
||||
bad "compound-literal slice: smoke fixture compiles (exit $status_compound)"
|
||||
echo "$out_compound"
|
||||
fi
|
||||
|
||||
if printf '%s' "$out_compound" | grep -qi 'error'; then
|
||||
bad "compound-literal smoke fixture: no compiler errors in output"
|
||||
else
|
||||
pass "compound-literal smoke fixture: no compiler errors in output"
|
||||
fi
|
||||
|
||||
# --- negative case: a deliberately invalid compound-literal-fixture
|
||||
# usage still fails ---
|
||||
BAD_COMPOUND_SRC="$REPO_ROOT/scripts/fixtures/_vc6_compound_literal_smoke_bad.c"
|
||||
cat > "$BAD_COMPOUND_SRC" <<'EOF'
|
||||
#include "Python.h"
|
||||
#include "pycore_initconfig.h"
|
||||
int bad(void) { return this_identifier_does_not_exist; }
|
||||
EOF
|
||||
trap 'rm -f "$BAD_SRC" "$BAD_BOOL_SRC" "$BAD_DECLS_SRC" "$BAD_DECL_ORDER_SRC" "$BAD_COMPOUND_SRC"' EXIT
|
||||
|
||||
out_compound_bad="$("$PROBE" scripts/fixtures/_vc6_compound_literal_smoke_bad.c 2>&1)"
|
||||
status_compound_bad=$?
|
||||
|
||||
if [ "$status_compound_bad" -ne 0 ]; then
|
||||
pass "probe-core reports nonzero exit for a broken compound-literal translation unit"
|
||||
else
|
||||
bad "probe-core reports nonzero exit for a broken compound-literal translation unit (got: $status_compound_bad)"
|
||||
fi
|
||||
|
||||
if printf '%s' "$out_compound_bad" | grep -q 'error C'; then
|
||||
pass "probe-core output surfaces the compiler diagnostic (compound-literal case)"
|
||||
else
|
||||
bad "probe-core output surfaces the compiler diagnostic (compound-literal case)"
|
||||
fi
|
||||
|
||||
# --- reference case: Modules/main.c itself no longer shows any C2059
|
||||
# "syntax error : '{'"/"syntax error : '.'" (the compound-literal/
|
||||
# designated-initializer error signature), and -- since this was the
|
||||
# last recorded blocker for main.c as a compile-only (/c) translation
|
||||
# unit -- now compiles with exit status 0. This is a compile-only
|
||||
# result: no link or runtime success is claimed (vc6-probe-core.sh is
|
||||
# /c only; see its header comment). ---
|
||||
if printf '%s' "$out_main" | grep -q 'main\.c([0-9]*) : error C2059'; then
|
||||
bad "Modules/main.c probe: does not regress to the compound-literal/designated-initializer C2059 blocker"
|
||||
else
|
||||
pass "Modules/main.c probe: does not regress to the compound-literal/designated-initializer C2059 blocker"
|
||||
fi
|
||||
|
||||
out_main_status="$("$PROBE" cpython/Modules/main.c >/dev/null 2>&1; echo $?)"
|
||||
if [ "$out_main_status" -eq 0 ]; then
|
||||
pass "Modules/main.c probe: compiles clean end-to-end (compile-only, exit 0)"
|
||||
else
|
||||
bad "Modules/main.c probe: compiles clean end-to-end (compile-only, got exit $out_main_status)"
|
||||
fi
|
||||
else
|
||||
echo "skip - wine/MSVC600 not available; skipping live probe-core integration checks"
|
||||
fi
|
||||
|
||||
if [ "$fail" -ne 0 ]; then
|
||||
echo "FAILED"
|
||||
exit 1
|
||||
fi
|
||||
echo "ALL TESTS PASSED"
|
||||
Executable
+107
@@ -0,0 +1,107 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Minimal reproducible MSVC 6.0 compile probe for the Windows 9x port.
|
||||
#
|
||||
# Runs the external MSVC600 `cl.exe` under Wine against the CPython 3.11.16
|
||||
# baseline in ./cpython, using the project-local compatibility headers in
|
||||
# ./compat/msvc600. Compile-only (/c): this probe measures how far the VC6
|
||||
# front end gets through the public headers, it does not attempt to link.
|
||||
#
|
||||
# The MSVC600 bundle is an external, local-only prerequisite; nothing under
|
||||
# MSVC600/ is part of this project's source.
|
||||
#
|
||||
# Usage:
|
||||
# scripts/vc6-probe.sh # probe Programs/python.c
|
||||
# scripts/vc6-probe.sh path/to/other.c # probe another translation unit
|
||||
#
|
||||
# Environment:
|
||||
# MSVC600_ROOT override the MSVC600 location (default: <repo>/MSVC600)
|
||||
# WINEPREFIX override the Wine prefix (default: ~/.wine-win9xport)
|
||||
|
||||
set -u
|
||||
|
||||
# Absolute POSIX path -> Wine drive Z: path.
|
||||
winpath() { printf 'Z:%s' "${1//\//\\}"; }
|
||||
|
||||
# Strip a literal (non-regex) prefix from every line of stdin.
|
||||
#
|
||||
# sed and bash glob patterns both treat backslashes in the *pattern* as
|
||||
# escape characters, so passing a Windows path (full of literal `\`
|
||||
# separators) as a sed/glob pattern silently fails to match: `\h`, `\u`,
|
||||
# etc. are consumed as (mostly no-op) escapes instead of literal
|
||||
# characters. Quoting the parameter inside a bash `${var//pattern/}`
|
||||
# expansion forces literal (non-glob) matching, which sidesteps the
|
||||
# problem entirely.
|
||||
strip_prefix() {
|
||||
local prefix="$1" line
|
||||
while IFS= read -r line || [ -n "$line" ]; do
|
||||
printf '%s\n' "${line//"$prefix"/}"
|
||||
done
|
||||
}
|
||||
|
||||
run_probe() {
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
MSVC600_ROOT="${MSVC600_ROOT:-$REPO_ROOT/MSVC600}"
|
||||
BUILD_DIR="$REPO_ROOT/build/vc6-probe"
|
||||
|
||||
SOURCE_REL="${1:-cpython/Programs/python.c}"
|
||||
SOURCE_ABS="$REPO_ROOT/$SOURCE_REL"
|
||||
|
||||
CL="$MSVC600_ROOT/VC98/Bin/CL.EXE"
|
||||
VCVARS="$MSVC600_ROOT/VC98/Bin/VCVARS32.BAT"
|
||||
|
||||
for required in "$CL" "$VCVARS" "$MSVC600_ROOT/VC98/Include" \
|
||||
"$MSVC600_ROOT/Common/MSDev98/Bin" "$SOURCE_ABS"; do
|
||||
if [ ! -e "$required" ]; then
|
||||
echo "vc6-probe: missing prerequisite: $required" >&2
|
||||
return 2
|
||||
fi
|
||||
done
|
||||
|
||||
if ! command -v wine >/dev/null 2>&1; then
|
||||
echo "vc6-probe: wine is not installed" >&2
|
||||
return 2
|
||||
fi
|
||||
|
||||
export WINEPREFIX="${WINEPREFIX:-$HOME/.wine-win9xport}"
|
||||
export WINEDEBUG="${WINEDEBUG:--all}"
|
||||
|
||||
# CL.EXE loads C1/C2 from VC98/Bin and MSPDB60.DLL from Common/MSDev98/Bin.
|
||||
export WINEPATH="$(winpath "$MSVC600_ROOT/VC98/Bin");$(winpath "$MSVC600_ROOT/Common/MSDev98/Bin")"
|
||||
|
||||
mkdir -p "$BUILD_DIR"
|
||||
|
||||
INCLUDE_ARGS=(
|
||||
"$(winpath "$REPO_ROOT/compat/msvc600")"
|
||||
"$(winpath "$REPO_ROOT/cpython/Include")"
|
||||
"$(winpath "$REPO_ROOT/cpython/PC")"
|
||||
"$(winpath "$MSVC600_ROOT/VC98/Include")"
|
||||
)
|
||||
|
||||
echo "== MSVC600 compile probe =="
|
||||
echo "source : $SOURCE_REL"
|
||||
echo "prefix : $WINEPREFIX"
|
||||
echo
|
||||
|
||||
CMD="call $(winpath "$VCVARS") && cd /d $(winpath "$REPO_ROOT/cpython") && cl /nologo"
|
||||
for include_arg in "${INCLUDE_ARGS[@]}"; do
|
||||
CMD+=" /I $include_arg"
|
||||
done
|
||||
CMD+=" /c $(winpath "$SOURCE_ABS") /Fo$(winpath "$BUILD_DIR")\\"
|
||||
|
||||
wine cmd /c "$CMD" 2>&1 |
|
||||
grep -v '^[0-9a-f]\{4\}:err:' |
|
||||
strip_prefix "$(winpath "$REPO_ROOT")\\"
|
||||
|
||||
status="${PIPESTATUS[0]}"
|
||||
echo
|
||||
echo "== cl.exe exit status: $status =="
|
||||
return "$status"
|
||||
}
|
||||
|
||||
# Allow `source scripts/vc6-probe.sh` (e.g. from tests) to pull in
|
||||
# winpath/strip_prefix without running the probe itself.
|
||||
if [ "${BASH_SOURCE[0]}" = "${0}" ]; then
|
||||
run_probe "$@"
|
||||
exit "$?"
|
||||
fi
|
||||
Executable
+107
@@ -0,0 +1,107 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Regression tests for scripts/vc6-probe.sh.
|
||||
#
|
||||
# Covers the two failure modes that made the probe's exit status and
|
||||
# output untrustworthy:
|
||||
# 1. strip_prefix() must remove a literal Windows-style path prefix
|
||||
# (backslash-separated) from probe output. A naive sed/glob
|
||||
# pattern silently no-ops on this because `\h`, `\u`, etc. are
|
||||
# consumed as escapes instead of literal characters.
|
||||
# 2. The reported exit status must be cl.exe's real status even
|
||||
# though its output passes through `grep` and `strip_prefix` in a
|
||||
# pipeline.
|
||||
#
|
||||
# Part 2 additionally runs the real probe against a deliberately
|
||||
# broken translation unit when wine + MSVC600 are available, so the
|
||||
# check exercises the actual pipeline, not just the helper functions.
|
||||
#
|
||||
# Usage: scripts/vc6-probe.test.sh
|
||||
|
||||
set -u
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
PROBE="$REPO_ROOT/scripts/vc6-probe.sh"
|
||||
|
||||
fail=0
|
||||
pass() { echo "ok - $1"; }
|
||||
bad() { echo "not ok - $1"; fail=1; }
|
||||
|
||||
# --- unit test: strip_prefix removes a literal backslash-heavy prefix ---
|
||||
|
||||
# shellcheck source=scripts/vc6-probe.sh
|
||||
source "$PROBE"
|
||||
|
||||
prefix='Z:\home\ubuntu\projects\python-win9x\'
|
||||
input="Z:\\home\\ubuntu\\projects\\python-win9x\\cpython\\Programs\\python.c(1) : error C2061"
|
||||
expected="cpython\\Programs\\python.c(1) : error C2061"
|
||||
actual="$(printf '%s\n' "$input" | strip_prefix "$prefix")"
|
||||
|
||||
if [ "$actual" = "$expected" ]; then
|
||||
pass "strip_prefix removes literal backslash-separated prefix"
|
||||
else
|
||||
bad "strip_prefix removes literal backslash-separated prefix (got: [$actual], want: [$expected])"
|
||||
fi
|
||||
|
||||
# A line that does not start with the prefix must be passed through
|
||||
# unchanged (no partial/garbled matches).
|
||||
input2="unrelated line with no prefix"
|
||||
actual2="$(printf '%s\n' "$input2" | strip_prefix "$prefix")"
|
||||
if [ "$actual2" = "$input2" ]; then
|
||||
pass "strip_prefix leaves non-matching lines untouched"
|
||||
else
|
||||
bad "strip_prefix leaves non-matching lines untouched (got: [$actual2])"
|
||||
fi
|
||||
|
||||
# --- unit test: pipeline exit status reflects the first command, not
|
||||
# grep/sed-equivalent downstream stages ---
|
||||
|
||||
fake_failure() { return 7; }
|
||||
fake_failure 2>&1 | grep -v '^nomatch' | strip_prefix "$prefix" >/dev/null
|
||||
status="${PIPESTATUS[0]}"
|
||||
if [ "$status" -eq 7 ]; then
|
||||
pass "PIPESTATUS[0] reports the producing command's exit status through the filter pipeline"
|
||||
else
|
||||
bad "PIPESTATUS[0] reports the producing command's exit status through the filter pipeline (got: $status)"
|
||||
fi
|
||||
|
||||
# --- integration test: run the real probe against a broken translation
|
||||
# unit, when the toolchain is actually available. Skipped (not
|
||||
# failed) otherwise, since CI/dev boxes without wine+MSVC600
|
||||
# cannot exercise this. ---
|
||||
|
||||
MSVC600_ROOT="${MSVC600_ROOT:-$REPO_ROOT/MSVC600}"
|
||||
if command -v wine >/dev/null 2>&1 && [ -e "$MSVC600_ROOT/VC98/Bin/CL.EXE" ]; then
|
||||
BAD_SRC="$REPO_ROOT/cpython/Programs/_vc6_probe_test_bad.c"
|
||||
printf 'this is not valid C !!! ###\n' > "$BAD_SRC"
|
||||
trap 'rm -f "$BAD_SRC"' EXIT
|
||||
|
||||
out="$("$PROBE" cpython/Programs/_vc6_probe_test_bad.c 2>&1)"
|
||||
real_status=$?
|
||||
|
||||
if [ "$real_status" -ne 0 ]; then
|
||||
pass "probe reports nonzero exit for a broken translation unit"
|
||||
else
|
||||
bad "probe reports nonzero exit for a broken translation unit (got: $real_status)"
|
||||
fi
|
||||
|
||||
if printf '%s' "$out" | grep -q 'error C'; then
|
||||
pass "probe output surfaces the compiler diagnostic"
|
||||
else
|
||||
bad "probe output surfaces the compiler diagnostic"
|
||||
fi
|
||||
|
||||
if printf '%s' "$out" | grep -q 'Z:\\home'; then
|
||||
bad "probe output leaks an unstripped absolute Wine path"
|
||||
else
|
||||
pass "probe output does not leak an unstripped absolute Wine path"
|
||||
fi
|
||||
else
|
||||
echo "skip - wine/MSVC600 not available; skipping live probe integration check"
|
||||
fi
|
||||
|
||||
if [ "$fail" -ne 0 ]; then
|
||||
echo "FAILED"
|
||||
exit 1
|
||||
fi
|
||||
echo "ALL TESTS PASSED"
|
||||
Reference in New Issue
Block a user