mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-18 00:58:30 +09:00
Merge branch 'p5b/r2' into p5b/integrate-codex
This commit is contained in:
+18
-20
@@ -962,6 +962,8 @@ jobs:
|
|||||||
# workflow passed that option. They had never been compiled by CI, let alone run.
|
# workflow passed that option. They had never been compiled by CI, let alone run.
|
||||||
- name: Unit tests on the split runtime
|
- name: Unit tests on the split runtime
|
||||||
working-directory: build-split
|
working-directory: build-split
|
||||||
|
env:
|
||||||
|
MOBILEGL_ITEST_REQUIRE_GPU: "1"
|
||||||
run: ctest --output-on-failure -L unit --no-tests=error -j "$(nproc)"
|
run: ctest --output-on-failure -L unit --no-tests=error -j "$(nproc)"
|
||||||
|
|
||||||
# --no-tests=error is half the gate, exactly as in integration-verify: the integration-split
|
# --no-tests=error is half the gate, exactly as in integration-verify: the integration-split
|
||||||
@@ -979,13 +981,11 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
ulimit -c unlimited
|
ulimit -c unlimited
|
||||||
sudo sysctl -w kernel.core_pattern='/tmp/core.%e.%p'
|
sudo sysctl -w kernel.core_pattern='/tmp/core.%e.%p'
|
||||||
ctest --output-on-failure -L integration-split --no-tests=error -j 4
|
ctest --output-on-failure -L integration-split --no-tests=error -j 4 --output-junit "${RUNNER_TEMP}/split-baseline.xml"
|
||||||
|
python3 ../scripts/ci/junit_tally.py "${RUNNER_TEMP}/split-baseline.xml" --require-split-ran
|
||||||
|
|
||||||
# ARCHITECTURE.md:521 asks for `ctest -L integration-gpu` to be name-for-name identical
|
# ID-65 supersedes broad inproc status parity: class-C aborts and named wrong-answer
|
||||||
# between the monolith and the split arm of the SAME build - the G2 shape extended to a
|
# debts are recorded. Monolith, integration-split and the controls remain hard gates.
|
||||||
# third arm. The knob goes in the JOB environment rather than in a ctest property, for the
|
|
||||||
# reason the MOBILEGL_ESPRYT_DISABLE_INVALIDATE_FLUSH precedent in `integration` gives: a
|
|
||||||
# property would override it and the arm would not be an arm.
|
|
||||||
#
|
#
|
||||||
# The entries that name MOBILEGL_TRANSPORT in their OWN property (the Split. lanes) keep
|
# The entries that name MOBILEGL_TRANSPORT in their OWN property (the Split. lanes) keep
|
||||||
# their value in both passes, which is correct: they are the split family in both arms and
|
# their value in both passes, which is correct: they are the split family in both arms and
|
||||||
@@ -1007,14 +1007,11 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
echo "integration-gpu entries in the split build: ${count}"
|
echo "integration-gpu entries in the split build: ${count}"
|
||||||
MOBILEGL_TRANSPORT=monolith ctest --output-on-failure -L integration-gpu --no-tests=error -j 4 --output-junit "${RUNNER_TEMP}/arm-monolith.xml"
|
MOBILEGL_TRANSPORT=monolith ctest --output-on-failure -L integration-gpu --no-tests=error -j 4 --output-junit "${RUNNER_TEMP}/arm-monolith.xml"
|
||||||
MOBILEGL_TRANSPORT=inproc ctest --output-on-failure -L integration-gpu --no-tests=error -j 4 --output-junit "${RUNNER_TEMP}/arm-inproc.xml"
|
# ID-65: broad inproc is a recorded debt census. Reduced split + controls gate below.
|
||||||
# NAME **AND STATUS**, and it is the comparison this step claimed to make and did not
|
inproc_rc=0
|
||||||
# (review finding N-3): the first version wrote a names file and never read it, and
|
MOBILEGL_TRANSPORT=inproc ctest --output-on-failure -L integration-gpu --no-tests=error -j 4 --output-junit "${RUNNER_TEMP}/arm-inproc.xml" || inproc_rc=$?
|
||||||
# `--output-on-failure` treats a SKIPPED test as not-a-failure - so the very failure
|
python3 ../scripts/ci/census_junit.py "${RUNNER_TEMP}/arm-inproc.xml" "${inproc_rc}" >> "${GITHUB_STEP_SUMMARY}"
|
||||||
# ARCHITECTURE.md:521 is about, "an inproc arm that skipped forty entries the monolith arm
|
# Retain the per-name status delta as evidence, not as the reduced-path gate.
|
||||||
# ran", was invisible here and caught only by the local gate. `ctest -N` cannot see it
|
|
||||||
# either: this is one build directory, so the two arms have identical name lists by
|
|
||||||
# construction and the difference is entirely in what each entry DID.
|
|
||||||
python3 - "${RUNNER_TEMP}/arm-monolith.xml" "${RUNNER_TEMP}/arm-inproc.xml" <<'PY'
|
python3 - "${RUNNER_TEMP}/arm-monolith.xml" "${RUNNER_TEMP}/arm-inproc.xml" <<'PY'
|
||||||
import sys, xml.etree.ElementTree as ET
|
import sys, xml.etree.ElementTree as ET
|
||||||
def rows(path):
|
def rows(path):
|
||||||
@@ -1031,12 +1028,9 @@ jobs:
|
|||||||
diff = sorted(set(a) ^ set(b)) + sorted(n for n in set(a) & set(b) if a[n] != b[n])
|
diff = sorted(set(a) ^ set(b)) + sorted(n for n in set(a) & set(b) if a[n] != b[n])
|
||||||
if diff:
|
if diff:
|
||||||
for name in diff[:40]:
|
for name in diff[:40]:
|
||||||
print(f"::error::{name}: monolith={a.get(name, '<absent>')} inproc={b.get(name, '<absent>')}")
|
print(f"{name}: monolith={a.get(name, '<absent>')} inproc={b.get(name, '<absent>')}")
|
||||||
print(f"::error::the monolith and inproc arms of ctest -L integration-gpu differ on "
|
print(f"Recorded ID-65 census: {len(diff)} name/status differences; not a parity gate")
|
||||||
f"{len(diff)} entries. ARCHITECTURE.md:521 requires them identical name for name "
|
else:
|
||||||
f"AND status; an entry that SKIPPED on one arm and ran on the other is the "
|
|
||||||
f"failure this compares for, and it is not a failure to --output-on-failure.")
|
|
||||||
raise SystemExit(1)
|
|
||||||
print(f"the two arms agree on all {len(a)} entries, name and status")
|
print(f"the two arms agree on all {len(a)} entries, name and status")
|
||||||
PY
|
PY
|
||||||
|
|
||||||
@@ -1095,6 +1089,7 @@ jobs:
|
|||||||
# and FAILED as evidence the lane was live, so the controls could be measured against a
|
# and FAILED as evidence the lane was live, so the controls could be measured against a
|
||||||
# baseline that was already red.
|
# baseline that was already red.
|
||||||
- name: Negative controls - the verb barrier and the persistent-map push must be load-bearing
|
- name: Negative controls - the verb barrier and the persistent-map push must be load-bearing
|
||||||
|
if: ${{ !cancelled() }}
|
||||||
working-directory: build-split
|
working-directory: build-split
|
||||||
env:
|
env:
|
||||||
MOBILEGL_ITEST_REQUIRE_GPU: "1"
|
MOBILEGL_ITEST_REQUIRE_GPU: "1"
|
||||||
@@ -1109,6 +1104,8 @@ jobs:
|
|||||||
path: |
|
path: |
|
||||||
build-split/MobileGL/MG_IntegrationTest/*.log*
|
build-split/MobileGL/MG_IntegrationTest/*.log*
|
||||||
build-split/MobileGL/MG_IntegrationTest/split-logs/*.log
|
build-split/MobileGL/MG_IntegrationTest/split-logs/*.log
|
||||||
|
${{ runner.temp }}/arm-inproc.xml
|
||||||
|
${{ runner.temp }}/arm-monolith.xml
|
||||||
if-no-files-found: warn
|
if-no-files-found: warn
|
||||||
|
|
||||||
- name: Upload core dumps
|
- name: Upload core dumps
|
||||||
@@ -1978,6 +1975,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
CONTROL_TMPDIR: ${{ runner.temp }}
|
CONTROL_TMPDIR: ${{ runner.temp }}
|
||||||
LIBRARY_LOG: ${{ matrix.case }}/${{ matrix.backend }}/output/mobilegl.log
|
LIBRARY_LOG: ${{ matrix.case }}/${{ matrix.backend }}/output/mobilegl.log
|
||||||
|
FROZEN_LIBRARY: ${{ github.workspace }}/build-linux/libMobileGL.so
|
||||||
run: >-
|
run: >-
|
||||||
bash "${GITHUB_WORKSPACE}/scripts/ci/retrace_drop_draw_control.sh"
|
bash "${GITHUB_WORKSPACE}/scripts/ci/retrace_drop_draw_control.sh"
|
||||||
'${{ matrix.case }}' '${{ matrix.backend }}'
|
'${{ matrix.case }}' '${{ matrix.backend }}'
|
||||||
|
|||||||
@@ -19,10 +19,21 @@
|
|||||||
|
|
||||||
#include <MG_Remote/Client/ClientSession.h>
|
#include <MG_Remote/Client/ClientSession.h>
|
||||||
#include <MG_Remote/Client/EmitTables.h>
|
#include <MG_Remote/Client/EmitTables.h>
|
||||||
|
#include <MG_Remote/Server/ServerLoop.h>
|
||||||
|
#include <chrono>
|
||||||
|
#include <thread>
|
||||||
#define MGITEST_SPLIT_RUNTIME_PEEK_LIVE 1
|
#define MGITEST_SPLIT_RUNTIME_PEEK_LIVE 1
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace MGITest {
|
namespace MGITest {
|
||||||
|
void DelaySplitRetirementForTesting(bool enabled) {
|
||||||
|
#if defined(MGITEST_SPLIT_RUNTIME_PEEK_LIVE)
|
||||||
|
MobileGL::MG_Remote::Server::ServerLoopInstance().SetBeforeRetireHookForTesting(
|
||||||
|
enabled ? +[] { std::this_thread::sleep_for(std::chrono::milliseconds(30)); } : nullptr);
|
||||||
|
#else
|
||||||
|
(void)enabled;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
SplitRuntimeState PeekSplitRuntime() {
|
SplitRuntimeState PeekSplitRuntime() {
|
||||||
SplitRuntimeState state;
|
SplitRuntimeState state;
|
||||||
|
|||||||
@@ -91,9 +91,8 @@ namespace MGITest {
|
|||||||
// than asserted: a uniform record stride over a power-of-two ring lands on the
|
// than asserted: a uniform record stride over a power-of-two ring lands on the
|
||||||
// boundary exactly and never straddles it.
|
// boundary exactly and never straddles it.
|
||||||
//
|
//
|
||||||
// stageReclaimWaits: SEG_STAGE allocations that only fitted after the encoder reclaimed
|
// stageReclaimWaits: allocations blocked after immediate reclamation failed;
|
||||||
// what the server had retired - P5's one real producer wait (see PipeWireCodec.h for
|
// already-retired bytes reclaimed lazily do not count as a wait.
|
||||||
// why the command ring has none while the verb barrier is armed).
|
|
||||||
unsigned long long maxRecordBytes = 0;
|
unsigned long long maxRecordBytes = 0;
|
||||||
unsigned long long maxRecordBytesCap = 0;
|
unsigned long long maxRecordBytesCap = 0;
|
||||||
unsigned long long cmdWraps = 0;
|
unsigned long long cmdWraps = 0;
|
||||||
@@ -103,6 +102,8 @@ namespace MGITest {
|
|||||||
};
|
};
|
||||||
|
|
||||||
SplitRuntimeState PeekSplitRuntime();
|
SplitRuntimeState PeekSplitRuntime();
|
||||||
|
// Scheduling-only perturbation; never changes a watermark or counter.
|
||||||
|
void DelaySplitRetirementForTesting(bool enabled);
|
||||||
|
|
||||||
// Empty when this process is a real split run that can be asserted about; otherwise the
|
// Empty when this process is a real split run that can be asserted about; otherwise the
|
||||||
// reason to GTEST_SKIP() with, naming the first fact that is not true and the package that
|
// reason to GTEST_SKIP() with, naming the first fact that is not true and the package that
|
||||||
|
|||||||
@@ -134,6 +134,9 @@ namespace MGITest::WireLedger {
|
|||||||
"kSmallRingLaneCmdByteTarget, which is sized for the 1 MiB ring this lane "
|
"kSmallRingLaneCmdByteTarget, which is sized for the 1 MiB ring this lane "
|
||||||
"declares (MGL_ITEST_GLES_SPLIT_SMALL_RING_ENVIRONMENT); a larger ring needs a "
|
"declares (MGL_ITEST_GLES_SPLIT_SMALL_RING_ENVIRONMENT); a larger ring needs a "
|
||||||
"larger workload and is not what this lane is for";
|
"larger workload and is not what this lane is for";
|
||||||
|
EXPECT_GE(state.stageReclaimWaits, 1u)
|
||||||
|
<< where << ": exit gate E3(e) - producer NEVER WAITED for staging retirement; "
|
||||||
|
"lazy reclamation of already-retired bytes is not back-pressure";
|
||||||
::testing::Test::RecordProperty("ring_wraps", static_cast<int>(state.cmdWraps));
|
::testing::Test::RecordProperty("ring_wraps", static_cast<int>(state.cmdWraps));
|
||||||
::testing::Test::RecordProperty("ring_wrap_pads", static_cast<int>(state.cmdWrapPads));
|
::testing::Test::RecordProperty("ring_wrap_pads", static_cast<int>(state.cmdWrapPads));
|
||||||
::testing::Test::RecordProperty("ring_waits", static_cast<int>(state.stageReclaimWaits));
|
::testing::Test::RecordProperty("ring_waits", static_cast<int>(state.stageReclaimWaits));
|
||||||
|
|||||||
@@ -68,6 +68,14 @@ def main():
|
|||||||
for n in selected for c in by_name[n])
|
for n in selected for c in by_name[n])
|
||||||
if not_failed:
|
if not_failed:
|
||||||
raise ValueError(f"{label} control: {not_failed} selected entries did not fail")
|
raise ValueError(f"{label} control: {not_failed} selected entries did not fail")
|
||||||
|
elif mode == "assertion":
|
||||||
|
cases = ET.parse(sys.argv[4]).getroot().findall(".//testcase")
|
||||||
|
by_name = {case.get("name"): case for case in cases}
|
||||||
|
for name in selected:
|
||||||
|
case = by_name.get(name)
|
||||||
|
output = "" if case is None else " ".join(" ".join(case.itertext()).split())
|
||||||
|
if not re.search(sys.argv[5], output):
|
||||||
|
raise ValueError(f"E3(a) FAILED: {name} red lacks its persistent-map push diagnostic")
|
||||||
elif mode == "evidence":
|
elif mode == "evidence":
|
||||||
missing = []
|
missing = []
|
||||||
label = sys.argv[5] if len(sys.argv) > 5 else ""
|
label = sys.argv[5] if len(sys.argv) > 5 else ""
|
||||||
|
|||||||
@@ -264,6 +264,21 @@ void main() { oColor = vec4(vColor, 1.0); }
|
|||||||
// the lane is that IT is the arm with a ring the workload can fill.
|
// the lane is that IT is the arm with a ring the workload can fill.
|
||||||
if (SplitLane::IsSmallRingLane()) {
|
if (SplitLane::IsSmallRingLane()) {
|
||||||
const unsigned long long driven = DriveUntilSmallRingOverruns();
|
const unsigned long long driven = DriveUntilSmallRingOverruns();
|
||||||
|
// Ordinary uploads, each fitting by itself, jointly exceed the lane's 1 MiB
|
||||||
|
// staging segment. Delay only scheduling between applied and retired: the
|
||||||
|
// production allocator, not this test, must observe capacity and wait.
|
||||||
|
GLuint pressureBuffer = 0;
|
||||||
|
glGenBuffers(1, &pressureBuffer);
|
||||||
|
glBindBuffer(GL_COPY_WRITE_BUFFER, pressureBuffer);
|
||||||
|
std::vector<unsigned char> upload(768 * 1024, 0x5a);
|
||||||
|
glBufferData(GL_COPY_WRITE_BUFFER, upload.size(), nullptr, GL_DYNAMIC_DRAW);
|
||||||
|
DelaySplitRetirementForTesting(true);
|
||||||
|
glBufferSubData(GL_COPY_WRITE_BUFFER, 0, upload.size(), upload.data());
|
||||||
|
upload[0] = 0xa5;
|
||||||
|
glBufferSubData(GL_COPY_WRITE_BUFFER, 0, upload.size(), upload.data());
|
||||||
|
DelaySplitRetirementForTesting(false);
|
||||||
|
glBindBuffer(GL_COPY_WRITE_BUFFER, 0);
|
||||||
|
glDeleteBuffers(1, &pressureBuffer);
|
||||||
Gl().EndFrame();
|
Gl().EndFrame();
|
||||||
WireLedger::ExpectSmallRingWrappedAtLeastOnce(
|
WireLedger::ExpectSmallRingWrappedAtLeastOnce(
|
||||||
"TriangleScenario.TheSameVboAndVaoRedrawAcrossAFrameBoundary", driven);
|
"TriangleScenario.TheSameVboAndVaoRedrawAcrossAFrameBoundary", driven);
|
||||||
|
|||||||
@@ -516,6 +516,7 @@ namespace MobileGL::MG_Remote::Client {
|
|||||||
// a live RingProducer over a cursor triple nobody consumes would be the
|
// a live RingProducer over a cursor triple nobody consumes would be the
|
||||||
// half-wired shape this session exists not to have.
|
// half-wired shape this session exists not to have.
|
||||||
m_encoder = Wire::PipeWireEncoder(control, &m_cmd, nullptr, &m_segments);
|
m_encoder = Wire::PipeWireEncoder(control, &m_cmd, nullptr, &m_segments);
|
||||||
|
m_encoder.SetStageRetirementDoorbell(m_producer.SelfDoorbell());
|
||||||
|
|
||||||
// ---- 7. the first CapsSnapshot, if the server had a backend to publish one from.
|
// ---- 7. the first CapsSnapshot, if the server had a backend to publish one from.
|
||||||
// ONE DRAIN, ONE ADOPTER (c1): PumpControlPlane below is the only thing in the client
|
// ONE DRAIN, ONE ADOPTER (c1): PumpControlPlane below is the only thing in the client
|
||||||
|
|||||||
@@ -530,6 +530,7 @@ namespace MobileGL::MG_Remote::Server {
|
|||||||
// a loop that applies and never retires ends the first MOBILEGL_IPC_STAGE_MB of
|
// a loop that applies and never retires ends the first MOBILEGL_IPC_STAGE_MB of
|
||||||
// staging in Fatal{RingOverrun, "SEG_STAGE"} (w1-v1 5). Once per drain batch, not
|
// staging in Fatal{RingOverrun, "SEG_STAGE"} (w1-v1 5). Once per drain batch, not
|
||||||
// once per record: retiring LATE is always legal, retiring EARLY never is.
|
// once per record: retiring LATE is always legal, retiring EARLY never is.
|
||||||
|
if (const auto hook = m_beforeRetireHook.load(std::memory_order_acquire)) hook();
|
||||||
consumer.RetireThrough(consumer.AppliedSeq());
|
consumer.RetireThrough(consumer.AppliedSeq());
|
||||||
// LEAVING THE APPLIER (p1's M-5) IS *NOT* DONE HERE. It is done inside
|
// LEAVING THE APPLIER (p1's M-5) IS *NOT* DONE HERE. It is done inside
|
||||||
// PipeApplier::ApplyOne, before s1's SessionConsumer::ApplyOne publishes appliedSeq
|
// PipeApplier::ApplyOne, before s1's SessionConsumer::ApplyOne publishes appliedSeq
|
||||||
|
|||||||
@@ -146,6 +146,11 @@ namespace MobileGL::MG_Remote::Server {
|
|||||||
// fail for its own reason.
|
// fail for its own reason.
|
||||||
Uint64 DrainedRecords() const;
|
Uint64 DrainedRecords() const;
|
||||||
Uint64 ParkCount() const;
|
Uint64 ParkCount() const;
|
||||||
|
// Scheduling perturbation only: the hook runs after application, before retirement.
|
||||||
|
// Integration tests use it to observe real producer back-pressure from GL uploads.
|
||||||
|
void SetBeforeRetireHookForTesting(void (*hook)()) {
|
||||||
|
m_beforeRetireHook.store(hook, std::memory_order_release);
|
||||||
|
}
|
||||||
|
|
||||||
// C7 / ID-54 diagnostics, read by ServerLoopTest's C7 and N-3 controls. NativeBindCount is
|
// C7 / ID-54 diagnostics, read by ServerLoopTest's C7 and N-3 controls. NativeBindCount is
|
||||||
// how many times ApplyMakeCurrent FORWARDED a bind to the backend (a tuple it did not
|
// how many times ApplyMakeCurrent FORWARDED a bind to the backend (a tuple it did not
|
||||||
@@ -230,6 +235,7 @@ namespace MobileGL::MG_Remote::Server {
|
|||||||
Uint64 m_affinityMask = 0;
|
Uint64 m_affinityMask = 0;
|
||||||
std::atomic<Uint64> m_drained{0};
|
std::atomic<Uint64> m_drained{0};
|
||||||
std::atomic<Uint64> m_parks{0};
|
std::atomic<Uint64> m_parks{0};
|
||||||
|
std::atomic<void (*)()> m_beforeRetireHook{nullptr};
|
||||||
|
|
||||||
// C7 / ID-54: the (dpy, draw, read, ctx) currently bound on the apply thread. Written and
|
// C7 / ID-54: the (dpy, draw, read, ctx) currently bound on the apply thread. Written and
|
||||||
// read ONLY on the apply thread inside ApplyMakeCurrent, so it needs no lock; the two
|
// read ONLY on the apply thread inside ApplyMakeCurrent, so it needs no lock; the two
|
||||||
|
|||||||
@@ -776,10 +776,12 @@ namespace MobileGL::MG_Remote::Wire {
|
|||||||
std::abort();
|
std::abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int attempt = 0; attempt < 2; ++attempt) {
|
bool reclaimed = false;
|
||||||
|
bool waited = false;
|
||||||
|
for (;;) {
|
||||||
// THE WRAP SKIP MAY ONLY BE CHARGED AGAINST BYTES THAT ARE STILL IN FLIGHT. When
|
// THE WRAP SKIP MAY ONLY BE CHARGED AGAINST BYTES THAT ARE STILL IN FLIGHT. When
|
||||||
// there are none the allocator starts over at offset zero, so a blob the segment
|
// there are none the allocator starts over at offset zero, so a blob the segment
|
||||||
// can hold whole is never refused (see RebaseEmptyStage). On attempt 1 this runs
|
// can hold whole is never refused (see RebaseEmptyStage). On retry this runs
|
||||||
// AFTER ReclaimStagedBytes, which is the case the finding describes: 8 MiB
|
// AFTER ReclaimStagedBytes, which is the case the finding describes: 8 MiB
|
||||||
// allocated, then retired, then a 28 MiB request that used to abort.
|
// allocated, then retired, then a 28 MiB request that used to abort.
|
||||||
RebaseEmptyStage();
|
RebaseEmptyStage();
|
||||||
@@ -793,21 +795,28 @@ namespace MobileGL::MG_Remote::Wire {
|
|||||||
m_stageHead += skip + need;
|
m_stageHead += skip + need;
|
||||||
return m_stageBase + at;
|
return m_stageBase + at;
|
||||||
}
|
}
|
||||||
if (attempt == 0) {
|
if (!reclaimed) {
|
||||||
// One try at reclaiming what the server has already retired. A second failure
|
// Lazy reclamation of already-retired bytes is NOT a producer wait.
|
||||||
// means the bytes genuinely do not fit, which R-10 says P5 does not chunk and
|
|
||||||
// must instead prove it never needs to.
|
|
||||||
//
|
|
||||||
// AND THIS IS P5'S ONE REAL BACK-PRESSURE EVENT, so it is counted here and
|
|
||||||
// published as `ringwaits=`. Reaching this line means the producer could not
|
|
||||||
// place a blob until the CONSUMER had retired earlier ones - the producer's
|
|
||||||
// progress depended on retiredSeq, which is exactly what R-9's "batching may
|
|
||||||
// only delay a watermark" is about. Exit gate E3(e)'s small-ring lane exists
|
|
||||||
// to make it happen at least once; a lane that never reaches it has a ring
|
|
||||||
// that is small only in its environment block.
|
|
||||||
++m_stageReclaimWaits;
|
|
||||||
ReclaimStagedBytes();
|
ReclaimStagedBytes();
|
||||||
|
reclaimed = true;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
if (m_stageRetirementBell == nullptr || m_stageMarkFront == m_stageMarks.size()) break;
|
||||||
|
const Uint64 pending = m_stageMarks[m_stageMarkFront].Seq;
|
||||||
|
const auto ready = [&] {
|
||||||
|
return m_control->retiredSeq.load(std::memory_order_acquire) >= pending;
|
||||||
|
};
|
||||||
|
if (!ready()) {
|
||||||
|
// The allocation still cannot progress after reclamation. Count this
|
||||||
|
// blocked allocation once, not each watermark poll or each reclaimed mark.
|
||||||
|
if (!waited) { ++m_stageReclaimWaits; waited = true; }
|
||||||
|
if (!m_stageRetirementBell->Wait(m_control->producerParked, ready, 0, 5000)) {
|
||||||
|
MGLOG_F("MGPipe: Fatal{RetirementWaitFailed, \"SEG_STAGE\"} producer wait "
|
||||||
|
"ended before the pending allocation retired (shutdown or timeout)");
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ReclaimStagedBytes();
|
||||||
}
|
}
|
||||||
MGLOG_F("MGPipe: Fatal{RingOverrun, \"SEG_STAGE\"} a %llu byte blob does not fit a %llu "
|
MGLOG_F("MGPipe: Fatal{RingOverrun, \"SEG_STAGE\"} a %llu byte blob does not fit a %llu "
|
||||||
"byte staging segment with %llu bytes still in flight (retiredSeq=%llu); P5 "
|
"byte staging segment with %llu bytes still in flight (retiredSeq=%llu); P5 "
|
||||||
|
|||||||
@@ -44,6 +44,7 @@
|
|||||||
#include <MG_Pipe/MGPipe.h>
|
#include <MG_Pipe/MGPipe.h>
|
||||||
|
|
||||||
#include "../Transport/Ring.h"
|
#include "../Transport/Ring.h"
|
||||||
|
#include "../Transport/Doorbell.h"
|
||||||
|
|
||||||
namespace MobileGL::MG_Remote::Wire {
|
namespace MobileGL::MG_Remote::Wire {
|
||||||
|
|
||||||
@@ -311,17 +312,16 @@ namespace MobileGL::MG_Remote::Wire {
|
|||||||
// this number would have been red for the arithmetic of the record catalogue rather
|
// this number would have been red for the arithmetic of the record catalogue rather
|
||||||
// than for anything about the ring.
|
// than for anything about the ring.
|
||||||
//
|
//
|
||||||
// `StageReclaimWaits()` counts every SEG_STAGE allocation that did not fit until the
|
// `StageReclaimWaits()` counts allocations blocked on an outstanding retiredSeq
|
||||||
// encoder reclaimed the runs the server had already retired - i.e. every time the
|
// after immediate reclamation still left insufficient space. Reclaiming bytes
|
||||||
// producer's progress depended on the consumer's retiredSeq. That is the honest
|
// the consumer had already retired does not increment it. One allocation counts
|
||||||
// back-pressure reading in P5, and the reason the command ring has none: the verb
|
// once even if it waits for several marks; this is staging, not command-ring pressure.
|
||||||
// barrier makes EmitAndWait wait for appliedSeq after EVERY record (R-1), so at most
|
|
||||||
// one record is ever in flight on SEG_CMD and a full command ring is not a wait but a
|
|
||||||
// Fatal{RingOverrun} (ClientSession.cpp). Publishing a "command ring waits" counter
|
|
||||||
// that can only ever be zero-or-dead is the decoration this file's counters are not.
|
|
||||||
Uint64 CmdWraps() const;
|
Uint64 CmdWraps() const;
|
||||||
Uint64 CmdWrapPads() const;
|
Uint64 CmdWrapPads() const;
|
||||||
Uint64 StageReclaimWaits() const;
|
Uint64 StageReclaimWaits() const;
|
||||||
|
// The live session supplies its shutdown-aware producer doorbell. Standalone codecs
|
||||||
|
// without a consumer cannot wait for retirement and retain the named refusal.
|
||||||
|
void SetStageRetirementDoorbell(Transport::Doorbell* bell) { m_stageRetirementBell = bell; }
|
||||||
|
|
||||||
// Bytes this encoder has ever written into SEG_CMD, pad fillers included: the
|
// Bytes this encoder has ever written into SEG_CMD, pad fillers included: the
|
||||||
// producer's monotonic head cursor. It is the DENOMINATOR the wrap count only means
|
// producer's monotonic head cursor. It is the DENOMINATOR the wrap count only means
|
||||||
@@ -365,6 +365,7 @@ namespace MobileGL::MG_Remote::Wire {
|
|||||||
Uint64 m_cmdWraps = 0;
|
Uint64 m_cmdWraps = 0;
|
||||||
Uint64 m_cmdWrapPads = 0;
|
Uint64 m_cmdWrapPads = 0;
|
||||||
Uint64 m_stageReclaimWaits = 0;
|
Uint64 m_stageReclaimWaits = 0;
|
||||||
|
Transport::Doorbell* m_stageRetirementBell = nullptr;
|
||||||
Vector<StageMark> m_stageMarks;
|
Vector<StageMark> m_stageMarks;
|
||||||
SizeT m_stageMarkFront = 0;
|
SizeT m_stageMarkFront = 0;
|
||||||
Uint8* m_stageBase = nullptr;
|
Uint8* m_stageBase = nullptr;
|
||||||
|
|||||||
@@ -1595,6 +1595,20 @@ TEST_F(PipeWireCodecTest, ABigProgramArchiveDoesNotGrowItsRecordAtAll) {
|
|||||||
EXPECT_GE(wire.Encoder().StagedBytesInFlight(), archive.size());
|
EXPECT_GE(wire.Encoder().StagedBytesInFlight(), archive.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(PipeWireCodecTest, AlreadyRetiredStagingReclamationIsNotAProducerWait) {
|
||||||
|
Wire2 wire;
|
||||||
|
std::vector<std::uint8_t> payload(Wire2::kStageBytes * 3 / 4, 0x5a);
|
||||||
|
wire.Encoder().StageBytes(payload.data(), payload.size());
|
||||||
|
MGPBindRenderState bind{};
|
||||||
|
ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::BindRenderState, &bind, sizeof(bind)), kInvalidSeq);
|
||||||
|
bool applied = false;
|
||||||
|
ASSERT_TRUE(wire.PumpOne(&applied));
|
||||||
|
// Do not explicitly reclaim: the second real allocation must do that itself.
|
||||||
|
wire.Encoder().StageBytes(payload.data(), payload.size());
|
||||||
|
EXPECT_EQ(wire.Encoder().StageReclaimWaits(), 0u)
|
||||||
|
<< "already-retired lazy reclamation is not a producer wait";
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(PipeWireCodecTest, StagedBytesAreReclaimedOnlyBehindRetiredSeq) {
|
TEST_F(PipeWireCodecTest, StagedBytesAreReclaimedOnlyBehindRetiredSeq) {
|
||||||
Wire2 wire;
|
Wire2 wire;
|
||||||
const std::uint8_t payload[64] = {};
|
const std::uint8_t payload[64] = {};
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ def cases():
|
|||||||
[SUITE+'BoundPackBufferOffsetReadRefusesByName'], 'RemoteClientTest'),
|
[SUITE+'BoundPackBufferOffsetReadRefusesByName'], 'RemoteClientTest'),
|
||||||
('codex12-repeat-skip', BACKEND,
|
('codex12-repeat-skip', BACKEND,
|
||||||
replace('if (draw != EGL_NO_SURFACE && ctx != EGL_NO_CONTEXT) {', 'if (false) {'),
|
replace('if (draw != EGL_NO_SURFACE && ctx != EGL_NO_CONTEXT) {', 'if (false) {'),
|
||||||
[SUITE+'RepeatedMakeCurrentAdoptsRepublishedCapsWithoutAPumpOrPresent'], 'RemoteClientTest'),
|
[SUITE+'ADifferentTupleMakeCurrentIsAdoptedWithoutAPumpOrPresent'], 'RemoteClientTest'),
|
||||||
('BlobMissing-optional-to-required', WIRE,
|
('BlobMissing-optional-to-required', WIRE,
|
||||||
replace('record.Blob = StageOptional(session, blobBytes, blobByteCount);',
|
replace('record.Blob = StageOptional(session, blobBytes, blobByteCount);',
|
||||||
'record.Blob = StageRequired(session, "SetDynamicState", blobBytes, blobByteCount);'),
|
'record.Blob = StageRequired(session, "SetDynamicState", blobBytes, blobByteCount);'),
|
||||||
@@ -122,13 +122,20 @@ def main():
|
|||||||
binary = ROOT / 'build-split/MobileGL/MG_Test' / ('Pipe' if target == 'PipeCatalogueTest' else 'Wire') / target
|
binary = ROOT / 'build-split/MobileGL/MG_Test' / ('Pipe' if target == 'PipeCatalogueTest' else 'Wire') / target
|
||||||
build = ['cmake', '--build', 'build-split', '-j', '24', '--target', target]
|
build = ['cmake', '--build', 'build-split', '-j', '24', '--target', target]
|
||||||
run = [str(binary), '--gtest_filter='+':'.join(names)]
|
run = [str(binary), '--gtest_filter='+':'.join(names)]
|
||||||
|
# ID-67: both replacement cases must execute green before/after this mutation.
|
||||||
|
# Suppressing client adoption only reddens the different-tuple case; an identical
|
||||||
|
# tuple correctly republishes nothing and must remain green under the perturbation.
|
||||||
|
green_names = names
|
||||||
|
if label == 'codex12-repeat-skip':
|
||||||
|
green_names = names + [SUITE+'AnIdenticalRepeatedMakeCurrentRepublishesNothing']
|
||||||
|
green_run = [str(binary), '--gtest_filter='+':'.join(green_names)]
|
||||||
print('\n=== '+label+' ===', flush=True)
|
print('\n=== '+label+' ===', flush=True)
|
||||||
try:
|
try:
|
||||||
brc, out = command(build)
|
brc, out = command(build)
|
||||||
if brc:
|
if brc:
|
||||||
raise RuntimeError('baseline build failed\n'+out)
|
raise RuntimeError('baseline build failed\n'+out)
|
||||||
rc, out = command(run)
|
rc, out = command(green_run)
|
||||||
if rc or any('[ OK ] '+name+' (' not in out for name in names):
|
if rc or any('[ OK ] '+name+' (' not in out for name in green_names):
|
||||||
raise RuntimeError('baseline not green\n'+out)
|
raise RuntimeError('baseline not green\n'+out)
|
||||||
path.write_text(mutate(original.decode()))
|
path.write_text(mutate(original.decode()))
|
||||||
brc, out = command(build)
|
brc, out = command(build)
|
||||||
@@ -146,12 +153,12 @@ def main():
|
|||||||
finally:
|
finally:
|
||||||
path.write_bytes(original)
|
path.write_bytes(original)
|
||||||
brc, out = command(build)
|
brc, out = command(build)
|
||||||
rc, out = command(run) if brc == 0 else (brc, out)
|
rc, out = command(green_run) if brc == 0 else (brc, out)
|
||||||
if rc or any('[ OK ] '+name+' (' not in out for name in names):
|
if rc or any('[ OK ] '+name+' (' not in out for name in green_names):
|
||||||
print('RESTORE-FAIL\n'+out, flush=True)
|
print('RESTORE-FAIL\n'+out, flush=True)
|
||||||
failures.append(label+' restore')
|
failures.append(label+' restore')
|
||||||
else:
|
else:
|
||||||
print('RESTORED GREEN: '+', '.join(names), flush=True)
|
print('RESTORED GREEN: '+', '.join(green_names), flush=True)
|
||||||
print('FAILED_CONTROLS='+repr(failures), flush=True)
|
print('FAILED_CONTROLS='+repr(failures), flush=True)
|
||||||
return bool(failures)
|
return bool(failures)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Summarize the indebted broad lane without turning debt into a hard gate."""
|
||||||
|
import collections
|
||||||
|
import sys
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
|
cases = ET.parse(sys.argv[1]).getroot().findall('.//testcase')
|
||||||
|
if not cases:
|
||||||
|
sys.exit('Census FAILED: no executed test records')
|
||||||
|
counts = collections.Counter()
|
||||||
|
for case in cases:
|
||||||
|
failure = case.find('failure')
|
||||||
|
if failure is not None:
|
||||||
|
reason = failure.get('message', '')
|
||||||
|
status = 'aborted' if 'aborted' in reason.lower() else 'failed'
|
||||||
|
elif case.find('skipped') is not None:
|
||||||
|
status = 'skipped'
|
||||||
|
else:
|
||||||
|
status = 'passed'
|
||||||
|
counts[status] += 1
|
||||||
|
print('### Broad inproc census (ID-65: recorded debt, not a gate)')
|
||||||
|
print(f'CTest exit: {sys.argv[2]}; total: {len(cases)}')
|
||||||
|
print('\n| passed | skipped | aborted | failed |\n|---:|---:|---:|---:|')
|
||||||
|
print('| ' + ' | '.join(str(counts[k]) for k in ('passed', 'skipped', 'aborted', 'failed')) + ' |')
|
||||||
@@ -56,10 +56,15 @@ run_split() { # $1 = STUB_MODE
|
|||||||
run_retrace() { # $1 = STUB_MODE
|
run_retrace() { # $1 = STUB_MODE
|
||||||
cd "${WORK}" || return 127
|
cd "${WORK}" || return 127
|
||||||
mkdir -p "${WORK}/OpenRA"
|
mkdir -p "${WORK}/OpenRA"
|
||||||
|
local rc=0
|
||||||
env -i PATH="${STUB_DIR}:/usr/bin:/bin" STUB_MODE="$1" \
|
env -i PATH="${STUB_DIR}:/usr/bin:/bin" STUB_MODE="$1" \
|
||||||
CTEST=ctest CONTROL_TMPDIR="${WORK}/tmp-$1" \
|
CTEST=ctest CONTROL_TMPDIR="${WORK}/tmp-$1" \
|
||||||
PULL_LIBRARY="${WORK}/pull.so" FROZEN_LIBRARY="${WORK}/frozen.so" \
|
PULL_LIBRARY="${WORK}/pull.so" FROZEN_LIBRARY="${WORK}/frozen.so" \
|
||||||
bash "${HERE}/retrace_pull_library_control.sh" OpenRA DirectGLES
|
bash "${HERE}/retrace_pull_library_control.sh" OpenRA DirectGLES || rc=$?
|
||||||
|
cmp -s "${WORK}/frozen.so" "${WORK}/split.so" || {
|
||||||
|
echo 'F6 FAILED: pull control did not restore the split library'; return 1;
|
||||||
|
}
|
||||||
|
return "${rc}"
|
||||||
}
|
}
|
||||||
|
|
||||||
run_drop_draw() { # $1 = STUB_MODE
|
run_drop_draw() { # $1 = STUB_MODE
|
||||||
@@ -67,7 +72,7 @@ run_drop_draw() { # $1 = STUB_MODE
|
|||||||
mkdir -p "${WORK}/OpenRA"
|
mkdir -p "${WORK}/OpenRA"
|
||||||
env -i PATH="${STUB_DIR}:/usr/bin:/bin" STUB_MODE="$1" \
|
env -i PATH="${STUB_DIR}:/usr/bin:/bin" STUB_MODE="$1" \
|
||||||
CTEST=ctest CONTROL_TMPDIR="${WORK}/tmp-$1" \
|
CTEST=ctest CONTROL_TMPDIR="${WORK}/tmp-$1" \
|
||||||
LIBRARY_LOG="${WORK}/tmp-$1/mobilegl.log" \
|
FROZEN_LIBRARY="${WORK}/frozen.so" LIBRARY_LOG="${WORK}/tmp-$1/mobilegl.log" \
|
||||||
bash "${HERE}/retrace_drop_draw_control.sh" OpenRA DirectGLES
|
bash "${HERE}/retrace_drop_draw_control.sh" OpenRA DirectGLES
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,8 +85,8 @@ expect PASSED "the scenarios' own diagnostic" -- run_split evidenc
|
|||||||
expect FAILED "the knob leaves the selection green" -- run_split green
|
expect FAILED "the knob leaves the selection green" -- run_split green
|
||||||
# The arming counter's half of the finding: a baseline that is already red cannot arm anything.
|
# The arming counter's half of the finding: a baseline that is already red cannot arm anything.
|
||||||
expect FAILED "the baseline is already red" -- run_split red-baseline
|
expect FAILED "the baseline is already red" -- run_split red-baseline
|
||||||
# The disarmed lane, which is a legitimate exit 0 while c1/s1/v1 are landing.
|
# P5 is complete: losing the runtime implementation must no longer disarm the gate.
|
||||||
expect PASSED "every split entry skipped (lane not armed)" -- run_split all-skipped
|
expect FAILED "every split entry skipped (implementation lost)" -- run_split all-skipped
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "=== the retrace lane's pull-library control (scripts/ci/retrace_pull_library_control.sh)"
|
echo "=== the retrace lane's pull-library control (scripts/ci/retrace_pull_library_control.sh)"
|
||||||
@@ -93,7 +98,9 @@ else
|
|||||||
echo "no cc available; the retrace half of this smoke test needs one" >&2
|
echo "no cc available; the retrace half of this smoke test needs one" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
: > "${WORK}/frozen.so"
|
printf '%s\n' 'int MG_Remote_stub(void) { return 1; }' > "${WORK}/split.c"
|
||||||
|
cc -shared -fPIC -o "${WORK}/frozen.so" "${WORK}/split.c" || exit 1
|
||||||
|
cp "${WORK}/frozen.so" "${WORK}/split.so"
|
||||||
|
|
||||||
# THE FINDING, part (b): a regex matching no tests. --no-tests=error exits non-zero and the old
|
# THE FINDING, part (b): a regex matching no tests. --no-tests=error exits non-zero and the old
|
||||||
# control read that as "the pull library turned it red".
|
# control read that as "the pull library turned it red".
|
||||||
|
|||||||
@@ -20,9 +20,11 @@ import sys
|
|||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
|
|
||||||
def tally(path):
|
def tally(path, split_only=False):
|
||||||
passed = failed = skipped = 0
|
passed = failed = skipped = 0
|
||||||
for case in ET.parse(path).getroot().iter('testcase'):
|
for case in ET.parse(path).getroot().iter('testcase'):
|
||||||
|
if split_only and not case.get('name', '').startswith('DirectGLES.Split.'):
|
||||||
|
continue
|
||||||
if case.find('failure') is not None or case.find('error') is not None:
|
if case.find('failure') is not None or case.find('error') is not None:
|
||||||
failed += 1
|
failed += 1
|
||||||
elif case.find('skipped') is not None or case.get('status') in ('notrun', 'disabled'):
|
elif case.find('skipped') is not None or case.get('status') in ('notrun', 'disabled'):
|
||||||
@@ -33,15 +35,18 @@ def tally(path):
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
if len(sys.argv) != 2:
|
if len(sys.argv) not in (2, 3) or (len(sys.argv) == 3 and sys.argv[2] != '--require-split-ran'):
|
||||||
print("usage: junit_tally.py <junit.xml>", file=sys.stderr)
|
print("usage: junit_tally.py <junit.xml> [--require-split-ran]", file=sys.stderr)
|
||||||
return 2
|
return 2
|
||||||
try:
|
try:
|
||||||
passed, failed, skipped = tally(sys.argv[1])
|
passed, failed, skipped = tally(sys.argv[1], split_only=len(sys.argv) == 3)
|
||||||
except Exception as exc: # a malformed file is not "zero of everything"
|
except Exception as exc: # a malformed file is not "zero of everything"
|
||||||
print(f"junit_tally: cannot parse {sys.argv[1]}: {exc}", file=sys.stderr)
|
print(f"junit_tally: cannot parse {sys.argv[1]}: {exc}", file=sys.stderr)
|
||||||
return 1
|
return 1
|
||||||
print(f"{passed} {failed} {skipped}")
|
print(f"{passed} {failed} {skipped}")
|
||||||
|
if len(sys.argv) == 3 and (passed == 0 or failed):
|
||||||
|
print('split baseline FAILED: no successful split runtime entries or an already-red selection', file=sys.stderr)
|
||||||
|
return 1
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ split, retrace, dropdraw = sys.argv[1], sys.argv[2], sys.argv[3]
|
|||||||
# other two catching the smoke test's "unrelated failure" case, and the case never flips - which
|
# other two catching the smoke test's "unrelated failure" case, and the case never flips - which
|
||||||
# is what this script measured the first time the perturbation actually applied.
|
# is what this script measured the first time the perturbation actually applied.
|
||||||
rules = [
|
rules = [
|
||||||
(split, [('LINE', 'grep -qE "${evidence}"', ('if ! ', 'elif ! ')),
|
(split, [('LINE', '"${log_helper}" assertion', ('if ! ', 'elif ! ')),
|
||||||
('SUBST', '"[A-Za-z_][A-Za-z_0-9]*"\\}\' || exit 1', '"[A-Za-z_][A-Za-z_0-9]*"\\}\' || true'),
|
('SUBST', '"[A-Za-z_][A-Za-z_0-9]*"\\}\' || exit 1', '"[A-Za-z_][A-Za-z_0-9]*"\\}\' || true'),
|
||||||
('SUBST', '"${private_evidence}" "${name}" || exit 1',
|
('SUBST', '"${private_evidence}" "${name}" || exit 1',
|
||||||
'"${private_evidence}" "${name}" || true')]),
|
'"${private_evidence}" "${name}" || true')]),
|
||||||
|
|||||||
@@ -50,6 +50,14 @@ CTEST="${CTEST:-ctest}"
|
|||||||
CONTROL_TMPDIR="${CONTROL_TMPDIR:-${RUNNER_TEMP:-/tmp}}"
|
CONTROL_TMPDIR="${CONTROL_TMPDIR:-${RUNNER_TEMP:-/tmp}}"
|
||||||
LIBRARY_LOG="${LIBRARY_LOG:-${CASE}/${BACKEND}/output/mobilegl.log}"
|
LIBRARY_LOG="${LIBRARY_LOG:-${CASE}/${BACKEND}/output/mobilegl.log}"
|
||||||
mkdir -p "${CONTROL_TMPDIR}"
|
mkdir -p "${CONTROL_TMPDIR}"
|
||||||
|
FROZEN_LIBRARY="${FROZEN_LIBRARY:?FROZEN_LIBRARY must name the split library the replay loads}"
|
||||||
|
symbols=$(nm --defined-only "${FROZEN_LIBRARY}") || exit 1
|
||||||
|
remote_count=$(printf '%s\n' "${symbols}" | grep -ic MG_Remote || true)
|
||||||
|
echo "draw-drop control library: ${FROZEN_LIBRARY}: MG_Remote=${remote_count}"
|
||||||
|
if [ "${remote_count}" -lt 1 ]; then
|
||||||
|
echo '::error::draw-drop control requires a split library: MG_Remote=0'
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
selector="^MobileGLTraceReplay\.${CASE}\.${BACKEND}$"
|
selector="^MobileGLTraceReplay\.${CASE}\.${BACKEND}$"
|
||||||
|
|
||||||
@@ -66,6 +74,8 @@ restore_good_output() {
|
|||||||
echo "restored the verified run's output over the control's"
|
echo "restored the verified run's output over the control's"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
trap restore_good_output EXIT
|
||||||
|
trap 'exit 130' INT TERM
|
||||||
|
|
||||||
matched=$("${CTEST}" -N -R "${selector}" | grep -cE '^ *Test *#[0-9]+:')
|
matched=$("${CTEST}" -N -R "${selector}" | grep -cE '^ *Test *#[0-9]+:')
|
||||||
if [ "${matched}" -lt 1 ]; then
|
if [ "${matched}" -lt 1 ]; then
|
||||||
|
|||||||
@@ -69,6 +69,18 @@ restore_good_output() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Restore the exact split library on success, failure, and interruption. The following
|
||||||
|
# draw-drop control uses this same frozen path.
|
||||||
|
saved_library=$(mktemp "${CONTROL_TMPDIR}/split-library.XXXXXX") || exit 1
|
||||||
|
cp -p "${FROZEN_LIBRARY}" "${saved_library}" || exit 1
|
||||||
|
restore_control() {
|
||||||
|
cp -p "${saved_library}" "${FROZEN_LIBRARY}"
|
||||||
|
rm -f "${saved_library}"
|
||||||
|
restore_good_output
|
||||||
|
}
|
||||||
|
trap restore_control EXIT
|
||||||
|
trap 'exit 130' INT TERM
|
||||||
|
|
||||||
# HOLE 1: COUNT THE SELECTION FIRST. `--no-tests=error` turns an empty selection into a non-zero
|
# HOLE 1: COUNT THE SELECTION FIRST. `--no-tests=error` turns an empty selection into a non-zero
|
||||||
# exit, which is indistinguishable from a working control unless the selection is counted.
|
# exit, which is indistinguishable from a working control unless the selection is counted.
|
||||||
matched=$("${CTEST}" -N -R "${selector}" | grep -cE '^ *Test *#[0-9]+:')
|
matched=$("${CTEST}" -N -R "${selector}" | grep -cE '^ *Test *#[0-9]+:')
|
||||||
@@ -82,8 +94,11 @@ fi
|
|||||||
# in. It defines no MG_Remote symbol, so ConfigLoader has no transport parser and
|
# in. It defines no MG_Remote symbol, so ConfigLoader has no transport parser and
|
||||||
# MOBILEGL_TRANSPORT=inproc is accepted and ignored - the exact shape of "the split lane ran
|
# MOBILEGL_TRANSPORT=inproc is accepted and ignored - the exact shape of "the split lane ran
|
||||||
# monolith".
|
# monolith".
|
||||||
cp "${PULL_LIBRARY}" "${FROZEN_LIBRARY}"
|
cp "${PULL_LIBRARY}" "${FROZEN_LIBRARY}" || exit 1
|
||||||
if nm --defined-only "${FROZEN_LIBRARY}" | grep -q -i MG_Remote; then
|
symbols=$(nm --defined-only "${FROZEN_LIBRARY}") || exit 1
|
||||||
|
remote_count=$(printf '%s\n' "${symbols}" | grep -ic MG_Remote || true)
|
||||||
|
echo "pull control library: ${FROZEN_LIBRARY}: MG_Remote=${remote_count}"
|
||||||
|
if [ "${remote_count}" -ne 0 ]; then
|
||||||
restore_good_output
|
restore_good_output
|
||||||
echo "::error::the control's own library defines MG_Remote symbols, so it is not a pull build and this control would prove nothing"
|
echo "::error::the control's own library defines MG_Remote symbols, so it is not a pull build and this control would prove nothing"
|
||||||
exit 1
|
exit 1
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ CONTROL_TMPDIR="${CONTROL_TMPDIR:-${RUNNER_TEMP:-/tmp}}"
|
|||||||
mkdir -p "${CONTROL_TMPDIR}"
|
mkdir -p "${CONTROL_TMPDIR}"
|
||||||
|
|
||||||
junit="${CONTROL_TMPDIR}/isplit.xml"
|
junit="${CONTROL_TMPDIR}/isplit.xml"
|
||||||
|
rm -f "${junit}"
|
||||||
log_helper="$(dirname "$0")/../../MobileGL/MG_IntegrationTest/Harness/split_log_paths.py"
|
log_helper="$(dirname "$0")/../../MobileGL/MG_IntegrationTest/Harness/split_log_paths.py"
|
||||||
|
|
||||||
# Check ownership even while the runtime lane is disarmed and will skip.
|
# Check ownership even while the runtime lane is disarmed and will skip.
|
||||||
@@ -87,14 +88,14 @@ echo "split entries - passed: ${baseline_passed}, failed: ${baseline_failed}, sk
|
|||||||
# A RED BASELINE DISARMS THE CONTROLS RATHER THAN ARMING THEM (review finding 8, second half).
|
# A RED BASELINE DISARMS THE CONTROLS RATHER THAN ARMING THEM (review finding 8, second half).
|
||||||
# `|| true` plus a "not skipped" counter used to treat a case that ran and FAILED as evidence the
|
# `|| true` plus a "not skipped" counter used to treat a case that ran and FAILED as evidence the
|
||||||
# lane was live. Turning an already-red entry red is not a measurement.
|
# lane was live. Turning an already-red entry red is not a measurement.
|
||||||
if [ "${baseline_failed}" -gt 0 ]; then
|
if [ "${baseline_failed}" -gt 0 ] || [ "${baseline_rc}" -ne 0 ]; then
|
||||||
echo "::error::${baseline_failed} DirectGLES.Split. entries are ALREADY RED with both knobs at their defaults, so neither negative control below can attribute its red to the knob it turns. Fix the lane first; a control measured against a red baseline is not a control. (This used to be swallowed by an unconditional '|| true' and counted as 'the lane is armed'.)"
|
echo "::error::${baseline_failed} DirectGLES.Split. entries are ALREADY RED with both knobs at their defaults, so neither negative control below can attribute its red to the knob it turns. Fix the lane first; a control measured against a red baseline is not a control. (This used to be swallowed by an unconditional '|| true' and counted as 'the lane is armed'.)"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "${baseline_passed}" -lt 1 ]; then
|
if [ "${baseline_passed}" -lt 1 ]; then
|
||||||
echo "::warning::every DirectGLES.Split. entry SKIPPED, so neither negative control can fire. The arming condition is a runtime fact - MG_Config::Transport, ClientSession::Active() and ImplementedVerbCount(), read by Harness/SplitRuntimePeek - and it becomes true on the commit that lands the last of c1/s1/v1. This step becomes a gate then, with no edit; it is not a green that asserted anything today."
|
echo "::error::split baseline FAILED: every DirectGLES.Split. entry SKIPPED; the split implementation did not execute"
|
||||||
exit 0
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ---- the controls ---------------------------------------------------------------------------
|
# ---- the controls ---------------------------------------------------------------------------
|
||||||
@@ -145,7 +146,7 @@ run_control() {
|
|||||||
if [ "${evidence}" = "private-barrier-fatal" ]; then
|
if [ "${evidence}" = "private-barrier-fatal" ]; then
|
||||||
python3 "${log_helper}" evidence "${manifest}" "${filter}" \
|
python3 "${log_helper}" evidence "${manifest}" "${filter}" \
|
||||||
'Fatal\{BarrierViolation, "[A-Za-z_][A-Za-z_0-9]*"\}' || exit 1
|
'Fatal\{BarrierViolation, "[A-Za-z_][A-Za-z_0-9]*"\}' || exit 1
|
||||||
elif ! tr -s '[:space:]' ' ' < "${out}" | grep -qE "${evidence}"; then
|
elif ! python3 "${log_helper}" assertion "${manifest}" "${filter}" "${result}" "${evidence}"; then
|
||||||
echo "::error::${name} FAILED: red lacks its persistent-map push diagnostic. Required: ${evidence}"
|
echo "::error::${name} FAILED: red lacks its persistent-map push diagnostic. Required: ${evidence}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
@@ -179,8 +180,10 @@ run_control "negative control E1 (MOBILEGL_IPC_VERB_BARRIER=0)" \
|
|||||||
# * the LIBRARY's own line in the entry's private file, saying the push was disabled by this
|
# * the LIBRARY's own line in the entry's private file, saying the push was disabled by this
|
||||||
# knob. It did not exist until ID-65 assigned it (joint-v1.md 3), which is why this control
|
# knob. It did not exist until ID-65 assigned it (joint-v1.md 3), which is why this control
|
||||||
# used to rest on the pixels alone.
|
# used to rest on the pixels alone.
|
||||||
|
# TheMapLandsInTheArmItsLaneDeclares skips by design outside PersistentMapArm.
|
||||||
|
# Select only the pixel cases; a pre-flight skip in either remains a hard failure.
|
||||||
run_control "negative control E3(a) (MOBILEGL_IPC_PERSISTENT_BLOCK_KB=0)" \
|
run_control "negative control E3(a) (MOBILEGL_IPC_PERSISTENT_BLOCK_KB=0)" \
|
||||||
'DirectGLES\.Split\.(SmallRing\.)?PersistentCoherentMapScenario' \
|
'DirectGLES\.Split\.(SmallRing\.)?PersistentCoherentMapScenario\.(TwoWritesThroughTheCoherentPointerEachReachTheirOwnDraw|AWriteAfterAFrameBoundaryReachesTheNextFramesDraw)$' \
|
||||||
"the SECOND write through the same mapping, announced by nothing|frame 1's write through the SAME mapping, after a Present" \
|
"the SECOND write through the same mapping, announced by nothing|frame 1's write through the SAME mapping, after a Present" \
|
||||||
'MGPipe: persistent-map push disabled - MOBILEGL_IPC_PERSISTENT_BLOCK_KB=0' \
|
'MGPipe: persistent-map push disabled - MOBILEGL_IPC_PERSISTENT_BLOCK_KB=0' \
|
||||||
MOBILEGL_IPC_PERSISTENT_BLOCK_KB=0
|
MOBILEGL_IPC_PERSISTENT_BLOCK_KB=0
|
||||||
|
|||||||
+2
-1
@@ -7,7 +7,7 @@ trap 'rm -rf "${WORK}"' EXIT
|
|||||||
cp "${HERE}/testdata/stub_ctest.sh" "${WORK}/ctest"
|
cp "${HERE}/testdata/stub_ctest.sh" "${WORK}/ctest"
|
||||||
chmod +x "${WORK}/ctest"
|
chmod +x "${WORK}/ctest"
|
||||||
passes=0
|
passes=0
|
||||||
for mode in missing-fatal stdout-fatal stale-fatal evidence e3-unrelated e3-no-private skipped-selection notrun-selection missing-selection partial-fatal wrong-fatal; do
|
for mode in missing-fatal stdout-fatal stale-fatal evidence e3-unrelated e3-no-private skipped-selection e3-skipped-selection notrun-selection missing-selection partial-fatal wrong-fatal; do
|
||||||
mkdir -p "${WORK}/${mode}"
|
mkdir -p "${WORK}/${mode}"
|
||||||
rc=0
|
rc=0
|
||||||
STUB_MODE="${mode}" CTEST="${WORK}/ctest" CONTROL_TMPDIR="${WORK}/${mode}" \
|
STUB_MODE="${mode}" CTEST="${WORK}/ctest" CONTROL_TMPDIR="${WORK}/${mode}" \
|
||||||
@@ -25,6 +25,7 @@ for mode in missing-fatal stdout-fatal stale-fatal evidence e3-unrelated e3-no-p
|
|||||||
case "${mode}" in
|
case "${mode}" in
|
||||||
e3-no-private) message='no selected private log carries /MGPipe: persistent-map push disabled' ;;
|
e3-no-private) message='no selected private log carries /MGPipe: persistent-map push disabled' ;;
|
||||||
skipped-selection) message='SplitLogPaths FAILED: E1 control: the knob killed the pre-flight, not the entry - 1 selected entries skipped' ;;
|
skipped-selection) message='SplitLogPaths FAILED: E1 control: the knob killed the pre-flight, not the entry - 1 selected entries skipped' ;;
|
||||||
|
e3-skipped-selection) message='SplitLogPaths FAILED: E3(a) control: the knob killed the pre-flight, not the entry - 1 selected entries skipped' ;;
|
||||||
notrun-selection|missing-selection) message='SplitLogPaths FAILED: E1 control: 1 selected entries did not run' ;;
|
notrun-selection|missing-selection) message='SplitLogPaths FAILED: E1 control: 1 selected entries did not run' ;;
|
||||||
esac
|
esac
|
||||||
if [[ "${mode}" = *-selection ]]; then
|
if [[ "${mode}" = *-selection ]]; then
|
||||||
|
|||||||
Vendored
+19
-2
@@ -24,7 +24,7 @@
|
|||||||
# never says the push was disabled (the half ID-65 added)
|
# never says the push was disabled (the half ID-65 added)
|
||||||
# green baseline green; the control's own run PASSES (the knob is not load-bearing)
|
# green baseline green; the control's own run PASSES (the knob is not load-bearing)
|
||||||
# red-baseline the baseline itself has a failed entry
|
# red-baseline the baseline itself has a failed entry
|
||||||
# all-skipped the baseline is entirely skipped (the disarmed lane, a legitimate exit 0)
|
# all-skipped the baseline is entirely skipped (lost implementation, a hard failure)
|
||||||
# retrace-noselect `ctest -N` matches nothing; the run exits 8 the way --no-tests=error does
|
# retrace-noselect `ctest -N` matches nothing; the run exits 8 the way --no-tests=error does
|
||||||
# retrace-unrelated one match; the run fails without naming the transport
|
# retrace-unrelated one match; the run fails without naming the transport
|
||||||
# retrace-evidence one match; the run fails with run_trace_case.cmake's own sentence
|
# retrace-evidence one match; the run fails with run_trace_case.cmake's own sentence
|
||||||
@@ -76,10 +76,20 @@ write_junit() {
|
|||||||
entry=DirectGLES.Split.ClearThenReadPixelsScenario.ClearWithNoDrawIsVisibleToDefaultFramebufferReadPixels
|
entry=DirectGLES.Split.ClearThenReadPixelsScenario.ClearWithNoDrawIsVisibleToDefaultFramebufferReadPixels
|
||||||
[ "${MOBILEGL_IPC_PERSISTENT_BLOCK_KB:-64}" != 0 ] || entry=DirectGLES.Split.PersistentCoherentMapScenario.TwoWritesThroughTheCoherentPointerEachReachTheirOwnDraw
|
[ "${MOBILEGL_IPC_PERSISTENT_BLOCK_KB:-64}" != 0 ] || entry=DirectGLES.Split.PersistentCoherentMapScenario.TwoWritesThroughTheCoherentPointerEachReachTheirOwnDraw
|
||||||
body="<testcase name=\"${entry}\" status=\"fail\"><failure message=\"control red\"/></testcase>"
|
body="<testcase name=\"${entry}\" status=\"fail\"><failure message=\"control red\"/></testcase>"
|
||||||
|
if [ "${MOBILEGL_IPC_PERSISTENT_BLOCK_KB:-64}" = 0 ]; then
|
||||||
|
case "${mode}" in
|
||||||
|
evidence|e3-no-private)
|
||||||
|
body="<testcase name=\"${entry}\" status=\"fail\"><failure/><system-out>the SECOND write through the same mapping, announced by nothing</system-out></testcase>" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
if [ "${mode}" = partial-fatal ] && [ "${MOBILEGL_IPC_VERB_BARRIER:-1}" = 0 ]; then
|
if [ "${mode}" = partial-fatal ] && [ "${MOBILEGL_IPC_VERB_BARRIER:-1}" = 0 ]; then
|
||||||
body="${body}<testcase name=\"DirectGLES.Split.TriangleScenario.SecondEntry\" status=\"fail\"><failure/></testcase>"
|
body="${body}<testcase name=\"DirectGLES.Split.TriangleScenario.SecondEntry\" status=\"fail\"><failure/></testcase>"
|
||||||
fi
|
fi
|
||||||
case "${mode}" in
|
case "${mode}" in
|
||||||
|
e3-skipped-selection)
|
||||||
|
if [ "${MOBILEGL_IPC_PERSISTENT_BLOCK_KB:-64}" = 0 ]; then
|
||||||
|
body="<testcase name=\"${entry}\" status=\"notrun\"><skipped/></testcase>"
|
||||||
|
fi ;;
|
||||||
skipped-selection) body="<testcase name=\"${entry}\" status=\"notrun\"><skipped/></testcase>" ;;
|
skipped-selection) body="<testcase name=\"${entry}\" status=\"notrun\"><skipped/></testcase>" ;;
|
||||||
notrun-selection) body="<testcase name=\"${entry}\" status=\"notrun\"/>" ;;
|
notrun-selection) body="<testcase name=\"${entry}\" status=\"notrun\"/>" ;;
|
||||||
missing-selection) body='' ;;
|
missing-selection) body='' ;;
|
||||||
@@ -130,7 +140,7 @@ fi
|
|||||||
# The control's own run.
|
# The control's own run.
|
||||||
if [ "${MOBILEGL_IPC_VERB_BARRIER:-1}" = 0 ]; then
|
if [ "${MOBILEGL_IPC_VERB_BARRIER:-1}" = 0 ]; then
|
||||||
case "${mode}" in
|
case "${mode}" in
|
||||||
evidence|e3-unrelated|e3-no-private|skipped-selection|notrun-selection|missing-selection|partial-fatal) echo 'Fatal{BarrierViolation, "DrawVbo"}' > "${log}" ;;
|
evidence|e3-unrelated|e3-no-private|skipped-selection|e3-skipped-selection|notrun-selection|missing-selection|partial-fatal) echo 'Fatal{BarrierViolation, "DrawVbo"}' > "${log}" ;;
|
||||||
wrong-fatal) echo 'Fatal{ReplyMissing, "DrawVbo"}' > "${log}" ;;
|
wrong-fatal) echo 'Fatal{ReplyMissing, "DrawVbo"}' > "${log}" ;;
|
||||||
missing-fatal) echo "library setup only; no fatal" > "${log}" ;;
|
missing-fatal) echo "library setup only; no fatal" > "${log}" ;;
|
||||||
stdout-fatal) echo 'Fatal{BarrierViolation, "DrawVbo"}' ;;
|
stdout-fatal) echo 'Fatal{BarrierViolation, "DrawVbo"}' ;;
|
||||||
@@ -144,6 +154,13 @@ if [ "${MOBILEGL_IPC_PERSISTENT_BLOCK_KB:-64}" = 0 ] && [ "${mode}" = evidence ]
|
|||||||
> "${CONTROL_TMPDIR}/pmap.log"
|
> "${CONTROL_TMPDIR}/pmap.log"
|
||||||
fi
|
fi
|
||||||
case "${mode}" in
|
case "${mode}" in
|
||||||
|
e3-skipped-selection)
|
||||||
|
if [ "${MOBILEGL_IPC_PERSISTENT_BLOCK_KB:-64}" = 0 ]; then
|
||||||
|
echo 'selected E3 entry ... ***Skipped'
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
exit 8
|
||||||
|
;;
|
||||||
skipped-selection|notrun-selection|missing-selection)
|
skipped-selection|notrun-selection|missing-selection)
|
||||||
echo '1/1 Test #1: selected entry ... ***Skipped'
|
echo '1/1 Test #1: selected entry ... ***Skipped'
|
||||||
echo '100% tests passed, 0 tests failed out of 1'
|
echo '100% tests passed, 0 tests failed out of 1'
|
||||||
|
|||||||
@@ -532,7 +532,8 @@ if [ "${1:-}" = "--self-test" ]; then
|
|||||||
printf '%s %s\n' \
|
printf '%s %s\n' \
|
||||||
"0000000000000000000000000000000000000000000000000000000000000000" "$target" \
|
"0000000000000000000000000000000000000000000000000000000000000000" "$target" \
|
||||||
>> "$WORK_DIR/pinprec.sha"
|
>> "$WORK_DIR/pinprec.sha"
|
||||||
apply_pinned_shas "$WORK_DIR/pinprec.sha" || exit 2
|
# Drive production extraction on a real historical ref whose body differs from the pin.
|
||||||
|
extract_baseline ff2994d9 pinprec || exit 2
|
||||||
got=$(awk -v n="$target" '$2 == n { print $1 }' "$WORK_DIR/pinprec.sha")
|
got=$(awk -v n="$target" '$2 == n { print $1 }' "$WORK_DIR/pinprec.sha")
|
||||||
if [ "$got" != "$pinned" ]; then
|
if [ "$got" != "$pinned" ]; then
|
||||||
say "PIN CONTROL FAILED: a baseline that carried a DIFFERENT sha for $target came out as"
|
say "PIN CONTROL FAILED: a baseline that carried a DIFFERENT sha for $target came out as"
|
||||||
@@ -552,7 +553,7 @@ if [ "${1:-}" = "--self-test" ]; then
|
|||||||
python3 "$PY" extract "$WORK_DIR/pinperturbed.cpp" "$ALL_FUNCTIONS" \
|
python3 "$PY" extract "$WORK_DIR/pinperturbed.cpp" "$ALL_FUNCTIONS" \
|
||||||
> "$WORK_DIR/pinperturbed.sha" || exit 2
|
> "$WORK_DIR/pinperturbed.sha" || exit 2
|
||||||
cp -f "$WORK_DIR/pristine.sha" "$WORK_DIR/pinbase.sha" || exit 2
|
cp -f "$WORK_DIR/pristine.sha" "$WORK_DIR/pinbase.sha" || exit 2
|
||||||
apply_pinned_shas "$WORK_DIR/pinbase.sha" || exit 2
|
extract_baseline HEAD pinbase || exit 2
|
||||||
if compare_lists "$WORK_DIR/pinbase.sha" "$WORK_DIR/pinperturbed.sha" \
|
if compare_lists "$WORK_DIR/pinbase.sha" "$WORK_DIR/pinperturbed.sha" \
|
||||||
"PIN($PINNED_BASELINE_REF)" "one-token-perturbed" 2> "$WORK_DIR/pinperturbed.err"; then
|
"PIN($PINNED_BASELINE_REF)" "one-token-perturbed" 2> "$WORK_DIR/pinperturbed.err"; then
|
||||||
say "NEGATIVE CONTROL DID NOT TRIP: one token was inserted into $target's body and the"
|
say "NEGATIVE CONTROL DID NOT TRIP: one token was inserted into $target's body and the"
|
||||||
|
|||||||
@@ -766,7 +766,8 @@ if [ "${1:-}" = "--self-test" ]; then
|
|||||||
printf '%s %s\n' \
|
printf '%s %s\n' \
|
||||||
"0000000000000000000000000000000000000000000000000000000000000000" "$target" \
|
"0000000000000000000000000000000000000000000000000000000000000000" "$target" \
|
||||||
>> "$WORK_DIR/pinprec.sha"
|
>> "$WORK_DIR/pinprec.sha"
|
||||||
apply_pinned_shas "$WORK_DIR/pinprec.sha" || exit 2
|
# Drive production extraction on a real historical ref whose body differs from the pin.
|
||||||
|
extract_baseline 37da3c3a pinprec || exit 2
|
||||||
got=$(awk -v n="$target" '$2 == n { print $1 }' "$WORK_DIR/pinprec.sha")
|
got=$(awk -v n="$target" '$2 == n { print $1 }' "$WORK_DIR/pinprec.sha")
|
||||||
if [ "$got" != "$pinned" ]; then
|
if [ "$got" != "$pinned" ]; then
|
||||||
say "PIN CONTROL FAILED: a baseline that carried a DIFFERENT sha for $target came out as"
|
say "PIN CONTROL FAILED: a baseline that carried a DIFFERENT sha for $target came out as"
|
||||||
@@ -789,7 +790,7 @@ if [ "${1:-}" = "--self-test" ]; then
|
|||||||
"$WORK_DIR/pinperturbed/$(blob_name "$targetSource")" token || exit 2
|
"$WORK_DIR/pinperturbed/$(blob_name "$targetSource")" token || exit 2
|
||||||
python3 "$PY" extract "$WORK_DIR/pinperturbed.spec" > "$WORK_DIR/pinperturbed.sha" || exit 2
|
python3 "$PY" extract "$WORK_DIR/pinperturbed.spec" > "$WORK_DIR/pinperturbed.sha" || exit 2
|
||||||
cp -f "$WORK_DIR/pristine.sha" "$WORK_DIR/pinbase.sha" || exit 2
|
cp -f "$WORK_DIR/pristine.sha" "$WORK_DIR/pinbase.sha" || exit 2
|
||||||
apply_pinned_shas "$WORK_DIR/pinbase.sha" || exit 2
|
extract_baseline HEAD pinbase || exit 2
|
||||||
if compare_lists "$WORK_DIR/pinbase.sha" "$WORK_DIR/pinperturbed.sha" \
|
if compare_lists "$WORK_DIR/pinbase.sha" "$WORK_DIR/pinperturbed.sha" \
|
||||||
"PIN($PINNED_BASELINE_REF)" "one-token-perturbed" 2> "$WORK_DIR/pinperturbed.err"; then
|
"PIN($PINNED_BASELINE_REF)" "one-token-perturbed" 2> "$WORK_DIR/pinperturbed.err"; then
|
||||||
say "NEGATIVE CONTROL DID NOT TRIP: one token was inserted into $target's body and the"
|
say "NEGATIVE CONTROL DID NOT TRIP: one token was inserted into $target's body and the"
|
||||||
|
|||||||
Reference in New Issue
Block a user