Skip to content

[3.0][Testing] Check that an upgrade survives being run twice and being interrupted - #9622

Merged
Sesquipedalian merged 2 commits into
SimpleMachines:release-3.0from
albertlast:3.0/upgrade-rerun-check
Sep 5, 2026
Merged

Sesquipedalian merged 2 commits into
SimpleMachines:release-3.0from
albertlast:3.0/upgrade-rerun-check

Conversation

@albertlast

@albertlast albertlast commented Sep 5, 2026 •

Copy link
Copy Markdown
Collaborator

Description

The upgrader has to cope with two things that happen to it constantly and that nothing tests: being run more than once, and being cut off half way. This adds a check for each, to the Docker development environment, alongside the existing install-forum.sh and reset.sh.

BASE=../SMF-2.1/.docker/baseline/artifacts/2.1.7-1/small/mysql.sql

.docker/rerun-upgrade.sh     --engine mysql --baseline "$BASE"
.docker/interrupt-upgrade.sh --engine mysql --baseline "$BASE"

rerun-upgrade.sh upgrades a 2.1 baseline, upgrades it again, and reports what the second run changed. It should change nothing. Running the upgrader twice is not the exotic case: it is what an admin does when a page times out, and it is what every 3.0 patch upgrade does, since VERSION_MAP keys on an upper bound and 3.0.99 selects the v3_0 migrations for any 3.0.x forum.

interrupt-upgrade.sh kills the upgrader at a chosen substep, starts it again, and reports whether the forum it ends up with is the one an uninterrupted upgrade would have built. --at N picks one substep; --points 10,50 a set; the default sweeps five points. Kill points are given as a percentage of an uninterrupted run's substeps, so the same numbers mean the same places whatever the baseline is and however many migrations the version grows.

Recovery here means starting again from the top, over a database in neither the old shape nor the new one. The step, substep and start position live in $_GET and nowhere else; maintenance_tool_progress in Settings.php is the only thing written to disk, it records the version the run started from rather than where it had reached, and it is written by preExit(), which a killed process never runs. Every migration therefore has to cope with a half-migrated database, which is a stronger requirement than merely being safe to repeat over a finished one.

--backup turns on the backup step, which the command line otherwise skips. It is worth turning on precisely because a retry is what endangers what it produces: backup_table() opens with DROP TABLE IF EXISTS, so a second pass replaces a good pre-upgrade copy with whatever the database holds by then.

The readings

Four, each written so the same database always produces the same bytes, since anything varying on its own would be reported as a change the upgrade made. Between them they separate the ways this goes wrong:

reading catches
the DDL of every table an ALTER that ran when it should not have, a table left half altered
the row count of each an INSERT with no guard on it, run twice
the settings table a setting rewritten in place, which a row count cannot see
the columns of the backup_ tables a good backup replaced by a retry — one taken before the migrations still carries the columns 3.0 removes

Timestamps that move on their own are filtered, as are the \restrict lines pg_dump 17 brackets its output with, which carry a key regenerated on every run.

What it found

Each of these has its own pull request:

  • an interrupted upgrade cannot be started again once HolidaysToEvents has dropped calendar_holidays, on both engines
  • PostgreSQL loses six indexes on upgrade, all on columns widened for 2038
  • PostgreSQL ends up with 80 duplicate index pairs out of 259
  • normalize() compares a column property, generated_expression, that exists under neither that name in the schema classes nor in what the database reports

It also needs #9302, without which a 2.1 → 3.0 upgrade does not complete at all on the current release-3.0 and neither script has anything to measure.

Not reachable from the unit suite

These drive upgrade.php against real MySQL and PostgreSQL databases in the Docker environment. tests/bootstrap.php provides no database and no request, so none of this is expressible as a PHPUnit test; it is a pair of scripts a person or CI runs, in the same shape as .docker/ci.sh. Nothing here is part of the shipped forum — it lives in .docker/, which the check-smf-index.php and check-smf-license.php checks skip.

Issues References (Fixes|Related|Closes)

  1. Related: [3.0] Lets an upgrade start again after the holidays table has gone #9626, [3.0] Rebuilds an index whose column the same pass has just changed #9623, [3.0] Recognises a normal index that is only under the wrong name #9624, [3.0] Compares the generation expression a column actually has #9625 -- everything below was found with these scripts
  2. Related: [3.0] Misc fixes #9302 -- needed before either script has anything to measure
  3. Related: [3.0][Testing] Check that an upgraded database matches a fresh install #9531 -- the other half of the question, whether an upgraded database matches a fresh install

albertlast and others added 2 commits September 5, 2026 10:14
The upgrader has to cope with two things that happen to it constantly and that
nothing tests: being run more than once, and being cut off half way.

Running twice is not the exotic case. It is what an admin does when a page
times out, and it is what every 3.0 patch upgrade does, since VERSION_MAP keys
on an upper bound and '3.0.99' selects the v3_0 migrations for any 3.0.x forum.
rerun-upgrade.sh upgrades a 2.1 baseline, upgrades it again, and reports what
the second run changed. It should change nothing.

Being interrupted is harder. The step, substep and start position live in $_GET
and nowhere else; maintenance_tool_progress in Settings.php is the only thing
written to disk, it records the version the run started from rather than where
it had reached, and it is written by preExit(), which a killed process never
runs. So recovery means starting again from the top over a database that is in
neither the old shape nor the new one. interrupt-upgrade.sh kills the upgrader
at a chosen substep, starts it again, and reports whether the forum it ends up
with is the one an uninterrupted upgrade would have built.

Kill points are given as a percentage of an uninterrupted run's substeps, so
the same numbers mean the same places whatever the baseline is and however many
migrations the version grows. --backup turns on the backup step, which the
command line otherwise skips: a retry is what endangers what it produces, since
backup_table() drops the backup table before writing it.

Four readings are taken of each database, each written so that the same
database always produces the same bytes: the DDL of every table, the row count
of each, the settings table, and the columns of the backup_ tables. Between
them they separate DDL that ran when it should not have from rows inserted
twice from a setting rewritten in place from a backup replaced by a retry.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: albertlast <mathiaspapealbert@hotmail.com>
Three things were being read as changes when the database had not changed.

pg_dump 17 brackets its output with \restrict and \unrestrict lines carrying a
key it generates afresh on every run, so two dumps of one untouched database
differed in two lines.

calendar_updated is written whenever the calendar is looked at, which puts it
with the other timestamps the settings reading already drops.

grep -c prints its count and then exits 1 when that count is zero, so the
`|| echo 0` fallback appended a second line and every numeric test on the
result failed. The kill points all came out as zero, every one of them was
skipped for being out of range, and the script reported that nothing had gone
wrong -- a pass told by an empty loop. The reference log is now required to
have substeps in it before any of that can happen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: albertlast <mathiaspapealbert@hotmail.com>
@Sesquipedalian
Sesquipedalian merged commit 5d353bc into SimpleMachines:release-3.0 Sep 5, 2026
9 checks passed
@jdarwood007 jdarwood007 added this to the 3.0 Alpha 5 milestone Sep 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants