Files
SplitBit-Emulator/Tests/makedisks.sh
T
AnachronautandClaude Opus 5 06af7e7fbb Reading a script must not move the person who started it
A script is fetched a block at a time while its lines run, and its name
is resolved afresh for every block. A name with a drive in front of it
moves the machine to that drive on the way past - sbfsWalk calls sbfsUse
- so a script found in the system's place on drive 0, started by somebody
standing on a disk of their own, ran its lines on the system disk.

Always possible with "do 0:/Apps/setup.sh", and reachable by typing a
name now that the search finds scripts the same three places it finds
programs. The drive is kept across each fetch and put back after it, at
both places a script's name is resolved.

The test has to work for it. A script that fits in one block is read
entirely while it is being opened, and the opening was never the hard
part; and the keep in scriptFill cannot be broken on its own, because
scriptOpen has already written the variable down. So the script on the
disk crosses two block boundaries and moves itself between them: what it
says about where it is standing is 1 before the move and 0 after.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
2026-09-06 13:23:31 -04:00

863 lines
51 KiB
Bash
Executable File

#!/usr/bin/env bash
# Builds the disk images the tests read from.
#
# These are made with SplitDisk, which is the other implementation of the same format.
# That is the point of them: a SplitBit program reading one of these is being checked
# against something written by different code from a written specification, rather than
# against itself.
#
# Written by Anachronaut
set -eu
# Made absolute before anything else. This script cds into its own working directory part
# of the way down, so a relative build path would be measured from the wrong place from
# there on - and the way that failed was not an error but a disk quietly missing some of
# the files it was supposed to have, which is a much worse thing to debug.
mkdir -p "$1"
BUILD="$(cd "$1" && pwd)"
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
TOOL="$ROOT/SplitDisk"
DISKS="$BUILD/disks"
WORK="$BUILD/.diskwork"
[ -x "$TOOL" ] || { echo "SplitDisk is not built."; exit 1; }
mkdir -p "$DISKS" "$WORK"
# Two directory blocks, so that a file can be put beyond the first one and the walk from
# block to block gets exercised rather than assumed.
"$TOOL" format "$DISKS/sbfs.img" 64 2 >/dev/null
cd "$WORK"
printf 'hello from a file' > greeting.txt
# Eight files fill the first directory block exactly, so everything after this lands in
# the second one.
for i in 1 2 3 4 5 6 7 8; do printf 'filler %d' "$i" > "filler$i.txt"; done
# Longer than a block, so reading it has to cross from one to the next. The pattern
# repeats every twenty six bytes, which makes a misplaced block obvious to read.
awk 'BEGIN { for (i = 0; i < 700; i++) printf "%c", 65 + (i % 26) }' > across.txt
: > empty.txt
printf 'exactly twenty two!!!!' > longname.txt
"$TOOL" put "$DISKS/sbfs.img" greeting.txt >/dev/null
for i in 1 2 3 4 5 6 7 8; do "$TOOL" put "$DISKS/sbfs.img" "filler$i.txt" >/dev/null; done
"$TOOL" put "$DISKS/sbfs.img" across.txt >/dev/null
"$TOOL" put "$DISKS/sbfs.img" empty.txt >/dev/null
"$TOOL" put "$DISKS/sbfs.img" longname.txt aName22CharactersLong! >/dev/null
# A disk with a loadable program on it. The program is assembled here rather than kept as
# bytes, so that what gets loaded is always built from the source beside it.
"$TOOL" format "$DISKS/load.img" 64 2 >/dev/null
# The assembler writes a loadable program itself, because the source says where it goes.
"$ROOT/Assembler" "$ROOT/Programs/Loader/loadable.asm" -o "$WORK/hello.sbx" >/dev/null
"$TOOL" put "$DISKS/load.img" "$WORK/hello.sbx" >/dev/null
# A disk for CosmOS. greet.sbx asks the system for everything it does rather than talking
# to the hardware itself, so loading and running it exercises the whole path: the loader,
# the vector table, the service handlers, and giving the machine back at the end.
#
# hello.sbx is the opposite case, and that is why it is here: it is the original
# hello.asm, written before any of this existed, and it still writes straight to port
# 0x00 rather than calling osPrintString. A program is allowed to reach past the system
# to the hardware, so something has to check that one still gives the machine back.
#
# notes.txt is there so that loading something that is not a program can be tried too.
# 512K, and eight directory blocks for 64 files. It was 64K and 16, which was ample for a
# disk that held programs and nothing else - but the native assembler reads SOURCE from
# here, and the CosmOS sources alone are 104,142 bytes against the 65,536 a 256 block disk
# holds. A machine that is going to assemble itself has to be able to hold its own source.
#
# Four blocks held 32 files and every one of them was taken, so adding an app failed the
# whole disk build - loudly, which is the right way round, but it is a wall that moves for
# free and there is no reason to stand next to it.
"$TOOL" format "$DISKS/cosmos.img" 2048 8 >/dev/null
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/greet.asm" -o "$WORK/greet.sbx" >/dev/null
"$TOOL" put "$DISKS/cosmos.img" "$WORK/greet.sbx" >/dev/null
# Assembled to a different working name so it cannot tread on load.img's hello.sbx above,
# then put under the name the shell asks for.
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/hello.asm" -o "$WORK/appHello.sbx" >/dev/null
"$TOOL" put "$DISKS/cosmos.img" "$WORK/appHello.sbx" hello.sbx >/dev/null
# Life.sbx is the one that had to be taught to stop. It runs to a still life and returns
# on its own, so the test needs no cycle limit: whether it ends is the thing being checked
# and a limit would hide the answer by supplying one.
"$ROOT/Assembler" -I "$ROOT/Programs/Libraries" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Life.asm" -o "$WORK/Life.sbx" >/dev/null
"$TOOL" put "$DISKS/cosmos.img" "$WORK/Life.sbx" >/dev/null
# Snake.sbx is the one that is played rather than watched. It reads the console a key at a
# time without ever waiting for one, so a script of moves drives it a move to a frame, and
# what is recorded is a whole game: turning, eating, growing, and running into a wall.
"$ROOT/Assembler" -I "$ROOT/Programs/Libraries" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Snake.asm" -o "$WORK/Snake.sbx" >/dev/null
"$TOOL" put "$DISKS/cosmos.img" "$WORK/Snake.sbx" >/dev/null
# Keys.sbx brings a vector of its own, which is what the version two format exists for. It
# is the only program here the system has to install anything for, so it is what says the
# whole path works: written into the file, installed at run, taken back out at exit.
"$ROOT/Assembler" -I "$ROOT/Programs/Libraries" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Keys.asm" -o "$WORK/Keys.sbx" >/dev/null
"$TOOL" put "$DISKS/cosmos.img" "$WORK/Keys.sbx" >/dev/null
# Say.sbx is the first program that can be told anything. Everything before it did the same
# thing however it was started.
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Say.asm" -o "$WORK/Say.sbx" >/dev/null
"$TOOL" put "$DISKS/cosmos.img" "$WORK/Say.sbx" >/dev/null
# Break.sbx stops itself twice and shows what the registers were each time. It needs no disk
# of its own: it only prints.
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Break.asm" -o "$WORK/Break.sbx" >/dev/null
"$TOOL" put "$DISKS/cosmos.img" "$WORK/Break.sbx" >/dev/null
# Grid.sbx is the first program that uses the screen as a screen rather than as somewhere to
# put letters: it redefines a tile, fills a map bigger than the display, and scrolls it by
# moving the origin. Tests/video.sh boots this disk and looks at the pixels.
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Grid.asm" -o "$WORK/Grid.sbx" >/dev/null
"$TOOL" put "$DISKS/cosmos.img" "$WORK/Grid.sbx" >/dev/null
# Press.sbx says what the console handed it, which is how the keys that are not characters
# are checked at all. It reads a line first and then keys, because line mode drops them and
# key mode delivers them and both halves of that rule want testing.
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Press.asm" -o "$WORK/Press.sbx" >/dev/null
"$TOOL" put "$DISKS/cosmos.img" "$WORK/Press.sbx" >/dev/null
# Mode.sbx has NO DATA SEGMENT, which is the whole reason it is here. Nothing else on this
# disk is that shape, and the loader used to stop the machine dead on it: a segment of no
# bytes asks the memory controller for the whole 64K, which does not fit, which it refuses.
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Mode.asm" -o "$WORK/Mode.sbx" >/dev/null
"$TOOL" put "$DISKS/cosmos.img" "$WORK/Mode.sbx" >/dev/null
# Flip.sbx draws a whole screen into the bank that is not being shown, waits, and then shows
# it. It is the only program here that leaves the machine displaying the OTHER screen, which
# is what makes it the thing that checks the system puts that back.
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Flip.asm" -o "$WORK/Flip.sbx" >/dev/null
"$TOOL" put "$DISKS/cosmos.img" "$WORK/Flip.sbx" >/dev/null
# Sprite.sbx moves a ball across the shell's own text without writing a byte of the map, and
# leaves the sprite in the table on the way out - so it is what says the system clears them.
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Sprite.asm" -o "$WORK/Sprite.sbx" >/dev/null
"$TOOL" put "$DISKS/cosmos.img" "$WORK/Sprite.sbx" >/dev/null
# Depth.sbx is the one that shows what a depth buffer is for: a ball behind two pillars and
# in front of two others, decided per column rather than by any ordering of the table.
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Depth.asm" -o "$WORK/Depth.sbx" >/dev/null
"$TOOL" put "$DISKS/cosmos.img" "$WORK/Depth.sbx" >/dev/null
# Lander.sbx is Lunar Porter, and the biggest program on the disk. It takes the screen, the
# mode and the map, so it is also the one that says a program can have all three back.
"$ROOT/Assembler" -I "$ROOT/Programs/Sounds" -I "$ROOT/Programs/Libraries" \
-I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Lander.asm" -o "$WORK/Lander.sbx" >/dev/null
"$TOOL" put "$DISKS/cosmos.img" "$WORK/Lander.sbx" >/dev/null
# Pad.sbx says what the controllers are doing, which is the only way to tell a pad nobody
# noticed from one that is mapped to nothing.
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Pad.asm" -o "$WORK/Pad.sbx" >/dev/null
"$TOOL" put "$DISKS/cosmos.img" "$WORK/Pad.sbx" >/dev/null
# Crash.sbx breaks in each of the four ways the system now catches, which is the only way to
# reach a fault screen from a recorded test.
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Crash.asm" -o "$WORK/Crash.sbx" >/dev/null
"$TOOL" put "$DISKS/cosmos.img" "$WORK/Crash.sbx" >/dev/null
# A script that names something nothing was ever set to. Stop on failure is what says the
# expansion really did fail rather than merely complaining: the line after it must not run.
printf '#! script\necho before\necho $nosuchname\necho after\n' > vars.script
# A script with blocks in it, because that is where they are actually for. It nests, it
# takes the second branch of an outer if, and the branch nobody takes mentions a name that
# was never set - which must not be an error, because nothing there is being run.
printf '#! script\nset colour red\nif same $colour red\n echo it is red\n if same $colour blue\n echo and blue\n else\n echo but not blue\n end\nelse\n echo $neverSetAnywhere\nend\nif same $colour blue\n echo wrong\nelse\n echo right\nend\nif same $colour blue\n if same $colour red\n echo deep wrong\n else\n echo deep also wrong\n end\nend\necho done\n' > blocks.script
"$TOOL" put "$DISKS/cosmos.img" vars.script >/dev/null
"$TOOL" put "$DISKS/cosmos.img" blocks.script >/dev/null
# Loops, which are what a block is for once there is more than one way out of it. Nested, and
# with an if inside one and a loop inside a branch nobody takes - which must run no times at
# all rather than once.
printf '#! script\nfor a in 1 2\n for b in x y\n echo $a$b\n end\nend\nset n go\nwhile same $n go\n echo round\n set n stop\nend\nfor c in p q\n if same $c p\n echo only p\n end\nend\nif same 1 2\n for d in never\n echo not this\n end\nend\nfor e in\n echo no words\nend\necho finished\n' > loops.script
"$TOOL" put "$DISKS/cosmos.img" loops.script >/dev/null
# ---- The splash Lander plays over its studio line ----
#
# Written in the tune language and compiled here, so the music can be changed without
# touching the game. Lander reads it off the disk at startup and starts anyway if it is not
# there, the way it treats /lander.state.
"$ROOT/SoundPatch" --blob "$ROOT/Programs/Sounds/Oboe.json" Oboe "$WORK/oboe.patch" >/dev/null
"$ROOT/TuneC" -I "$WORK" "$ROOT/Programs/Tunes/splash.score" "$WORK/splash.tune" >/dev/null
"$TOOL" put "$DISKS/cosmos.img" "$WORK/splash.tune" splash.tune >/dev/null
# tune.sbx, which used to be a boot image and is now a program like any other. It brings a
# vector of its own - the timer's, so that it has a beat to play to - which makes it the
# second thing here that the version two format exists for.
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/Examples/tune.asm" -o "$WORK/tune.sbx" >/dev/null
"$TOOL" put "$DISKS/cosmos.img" "$WORK/tune.sbx" >/dev/null
# And Play, which is the same beat carrying four voices instead of one - each with an
# instrument of its own out of Sounds, which is what four patches at once looks like.
"$ROOT/Assembler" -I "$ROOT/Programs/Sounds" -I "$ROOT/Programs/Libraries" \
-I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Play.asm" -o "$WORK/Play.sbx" >/dev/null
"$TOOL" put "$DISKS/cosmos.img" "$WORK/Play.sbx" >/dev/null
printf 'this is not a program' > notes.txt
"$TOOL" put "$DISKS/cosmos.img" notes.txt >/dev/null
# ---- A second disk, for the second drive ----
#
# Deliberately nothing like the first: its own file and its own directory, so that a test
# looking at one cannot be looking at the other by accident. Small, because what is on it
# matters and how much does not.
"$TOOL" format "$DISKS/other.img" 512 4 >/dev/null
printf 'this lives on the other disk' > other.txt
"$TOOL" put "$DISKS/other.img" other.txt >/dev/null
"$TOOL" mkdir "$DISKS/other.img" /notes >/dev/null
# A name that begins with a digit, because a drive prefix is a digit and a colon and the
# colon is the whole of what tells them apart. Without it this would be drive 2.
"$TOOL" mkdir "$DISKS/other.img" /2things >/dev/null
# A file of more than one block, because a cross-drive copy of a single block never looks
# the path up twice and so never meets the cache that used to hand back the right blocks of
# the wrong disk.
python3 -c "open('twoblocks.txt','w').write('the second disk, at length. ' * 20)"
"$TOOL" put "$DISKS/other.img" twoblocks.txt >/dev/null
# ---- And a /Apps on drive 0, because that is where a system keeps its programs ----
#
# The apps above sit at the root of this image, which is where they were put before there
# were directories and is fine for finding a program on the disk you are standing on. The
# THIRD place the shell looks is /Apps on drive 0, and nothing could exercise it while this
# disk had no such place.
"$TOOL" mkdir "$DISKS/cosmos.img" /Apps >/dev/null
"$ROOT/Assembler" -I "$ROOT/Programs/Libraries" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Copy.asm" -o "$WORK/Copy.sbx" >/dev/null
"$TOOL" put "$DISKS/cosmos.img" "$WORK/Copy.sbx" /Apps/Copy.sbx >/dev/null
"$TOOL" put "$DISKS/cosmos.img" "$WORK/Say.sbx" /Apps/Say.sbx >/dev/null
# A SCRIPT in the system's place, which is the one thing on this disk that is fetched from
# drive 0 a block at a time WHILE it runs. Its own name carries the drive in front of it,
# and resolving a name like that moves the machine to that drive - so a script started from
# another disk would run its lines on this one unless something puts the drive back between
# every block. All it does is say where it thinks it is, twice, with a move in between.
python3 -c "
# LONGER THAN A BLOCK on purpose, twice over. A script that fits in one block is read
# entirely while it is being opened, and the opening is not the hard part - the refills are,
# because they happen after the drive has been given back to whoever typed the name.
#
# And it moves itself between the two refills, which is the second half of the same rule: a
# script that goes to another disk has to stay there, so a refill puts back the drive the
# script left the machine on and not the one it was opened from.
def pad(so_far):
out = ''
while so_far + len(out) < 300:
out += '; pad\\n'
return out
one = '#!\\necho the script is running\\ndrive\\n'
two = 'drive 0\\n'
open('where.sh', 'w').write(one + pad(len(one)) + two + pad(len(two)) + 'drive\\n')"
"$TOOL" put "$DISKS/cosmos.img" where.sh /Apps/where.sh >/dev/null
# ---- Scripts, including the ones that are meant to go wrong ----
#
# Built here rather than committed, because two of them are about BYTE POSITIONS and a
# committed file is one an editor can helpfully repair. cross.script puts a command across
# the 256 byte boundary between two blocks, and nonl.script ends without a newline; both
# were real faults before they were checks, and both are invisible in a text editor.
printf '#! script\n; A comment, which never reaches the shell.\necho saying what it is doing\nSay from a script\n\necho\nSay and again\n' > hi.script
"$TOOL" put "$DISKS/cosmos.img" hi.script >/dev/null
# Stops at the bad line, so "after" must not appear.
printf '#! script\nSay before\nnosuchcommand\nSay after\n' > bad.script
"$TOOL" put "$DISKS/cosmos.img" bad.script >/dev/null
# No #! on the front, so it is a text file and not a script.
printf 'Say this has no shebang\n' > plain.script
"$TOOL" put "$DISKS/cosmos.img" plain.script >/dev/null
# A command that starts in the first block and finishes in the second.
python3 -c "
head = '#! script\n'
pad = ''
while len(head) + len(pad) < 246:
pad += '; pad\n'
open('cross.script','w').write(head + pad + 'Say across the block boundary\n')
"
"$TOOL" put "$DISKS/cosmos.img" cross.script >/dev/null
# The last line has no newline after it and still has to run.
printf '#! script\nSay with no newline after me' > nonl.script
"$TOOL" put "$DISKS/cosmos.img" nonl.script >/dev/null
# ---- One script inside another ----
#
# outer.script resumes in its SECOND block, which is the case the whole design turns on: the
# inner script reads its own block into the one buffer there is, so coming back means reading
# the outer one's block again and landing on the byte it left off at. Generated, because
# where the do line falls is the entire point and no editor should be able to move it.
python3 -c "
head = '#! script\n'
pad = ''
while len(head) + len(pad) < 300:
pad += '; pad\n'
open('outer.script','w').write(head + pad +
'echo outer in block one\ndo inner.script\necho outer resumed in block one\n')
"
"$TOOL" put "$DISKS/cosmos.img" outer.script >/dev/null
printf '#! script\necho inner one\necho inner two\n' > inner.script
"$TOOL" put "$DISKS/cosmos.img" inner.script >/dev/null
# A script that runs itself, which is what the depth limit is for.
printf '#! script\necho self\ndo loop.script\n' > loop.script
"$TOOL" put "$DISKS/cosmos.img" loop.script >/dev/null
# ---- A disk that starts itself ----
#
# Its own image, and that is the point rather than an inconvenience: a startup script runs on
# every boot, so putting one on the disk the other twenty tests use would prepend it to all of
# them. This is that disk with /System/Boot/startup.sh added.
cp "$DISKS/cosmos.img" "$DISKS/startup.img"
"$TOOL" mkdir "$DISKS/startup.img" /System >/dev/null
"$TOOL" mkdir "$DISKS/startup.img" /System/Boot >/dev/null
# It ENDS QUIET on purpose. Nothing puts the flag back when the outermost script finishes -
# there is no caller to restore it from - so a script ending quiet is the case that catches a
# shell left with no prompt for whoever types next. Ending #loud tests nothing.
printf '#! script\n#quiet\necho Segan Voyager\necho CosmOS ready.\n#loud\necho and loud again\n#quiet\necho and quiet again at the end\n' > startup.sh
"$TOOL" put "$DISKS/startup.img" startup.sh /System/Boot/startup.sh >/dev/null
# The same disk with something at that name that is not a script, which must say so rather
# than run it or ignore it.
cp "$DISKS/cosmos.img" "$DISKS/startupbad.img"
"$TOOL" mkdir "$DISKS/startupbad.img" /System >/dev/null
"$TOOL" mkdir "$DISKS/startupbad.img" /System/Boot >/dev/null
printf 'echo I have no shebang\n' > startupbad.sh
"$TOOL" put "$DISKS/startupbad.img" startupbad.sh /System/Boot/startup.sh >/dev/null
# A disk of its own for the writing test, with one file already on it so that what it
# writes has to be placed somewhere that does not tread on what is there.
"$TOOL" format "$DISKS/write.img" 32 1 >/dev/null
printf 'already here' > here.txt
"$TOOL" put "$DISKS/write.img" here.txt >/dev/null
# A disk of its own for the editing test, which deletes and renames things and would
# otherwise leave the writing test's disk looking nothing like the writing test expects.
# It starts with one file on it so that a document written here has to be placed around
# something, and so that the disk can be compared afterwards against one that never had
# any of it: the metadata should come back exactly as it was.
"$TOOL" format "$DISKS/edit.img" 32 1 >/dev/null
"$TOOL" put "$DISKS/edit.img" here.txt >/dev/null
# A disk for the shell's delete and rename, which is its own because those change what is
# on it. A fixture whose name has a directory in it is used as it stands rather than being
# made fresh per test, so a test that writes to a shared one would quietly change what
# every test after it sees.
"$TOOL" format "$DISKS/files.img" 32 1 >/dev/null
printf 'the first one' > one.txt
printf 'the second one' > two.txt
"$TOOL" put "$DISKS/files.img" one.txt >/dev/null
"$TOOL" put "$DISKS/files.img" two.txt >/dev/null
# A disk for the editor, holding nothing but the editor. Its own, because the whole point
# of it is that it writes: a document made on a shared fixture would turn up in the file
# listing of every test that came after it.
"$TOOL" format "$DISKS/editor.img" 256 2 >/dev/null
"$ROOT/Assembler" -I "$ROOT/Programs/Libraries" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Edit.asm" -o "$WORK/Edit.sbx" >/dev/null
"$TOOL" put "$DISKS/editor.img" "$WORK/Edit.sbx" >/dev/null
# A disk that has directories on it AND gets written to, which nothing else covers: every
# other version two fixture is read only, and every fixture that is written to is flat.
# Making a file runs the allocator over a directory entry, and a directory has no blocks,
# so it must be in nobody's way while a run of free ones is looked for. Its own disk, for
# the same reason the editor has one: what it writes would otherwise turn up in the
# listing of every test that came after it.
"$TOOL" format "$DISKS/treewrite.img" 256 2 >/dev/null
"$TOOL" mkdir "$DISKS/treewrite.img" /Folder >/dev/null
"$TOOL" mkdir "$DISKS/treewrite.img" /Folder/Inner >/dev/null
"$TOOL" put "$DISKS/treewrite.img" "$WORK/Edit.sbx" >/dev/null
# A disk for the file services, holding nothing but the program that exercises them. Its
# own, because that program writes: it tidies up after itself, but a run that stopped part
# way would leave a document behind on a fixture every later test reads.
"$TOOL" format "$DISKS/services.img" 256 2 >/dev/null
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Files.asm" -o "$WORK/Files.sbx" >/dev/null
"$TOOL" put "$DISKS/services.img" "$WORK/Files.sbx" >/dev/null
# A disk for streaming, and the only one here that has to be big: the file on it is bigger
# than the machine's Data Memory, which is the entire point of the services it checks.
#
# THE FILE IS GENERATED RATHER THAN TAKEN FROM THE REPOSITORY. Concatenating the CosmOS
# sources would be a truer picture of what streaming is for, and would change the recorded
# checksum every time a line of CosmOS was edited - so a real difference would arrive in a
# crowd of meaningless ones, which is the same trap the cycle counts used to set. This is
# 84000 bytes, which is 328 whole blocks and 32 bytes over, so the short block at the end
# is exercised rather than assumed.
"$TOOL" format "$DISKS/stream.img" 1024 2 >/dev/null
awk 'BEGIN { for (i = 0; i < 4000; i++) printf "streaming line %05d\n", i }' > big.txt
awk 'BEGIN { for (i = 0; i < 50; i++) printf "small %05d\n", i }' > small.txt
"$TOOL" put "$DISKS/stream.img" big.txt >/dev/null
"$TOOL" put "$DISKS/stream.img" small.txt >/dev/null
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Stream.asm" -o "$WORK/Stream.sbx" >/dev/null
"$TOOL" put "$DISKS/stream.img" "$WORK/Stream.sbx" >/dev/null
# A disk for Type. The first file crosses several blocks and deliberately has no zero byte
# for the application to mistake for an end marker. The second proves that zero blocks is
# a valid empty file rather than an error.
"$TOOL" format "$DISKS/type.img" 64 2 >/dev/null
# SIXTY LINES, and the number matters. More shows a screenful less the prompt, which is 22
# rows on the forty column screen and 47 on the eighty column one - so a file of 32 lines
# stopped being a test of paging the day More learned to ask how tall the screen was. It fits
# in one page now. Sixty does not fit in either.
printf 'first line\nsecond line\n' > readable.txt
awk 'BEGIN { for (i = 0; i < 60; i++) printf "line %02d: ABCDEFGHIJKLMNOPQRSTUVWXYZ\n", i }' >> readable.txt
: > empty.txt
"$TOOL" put "$DISKS/type.img" readable.txt >/dev/null
"$TOOL" put "$DISKS/type.img" empty.txt >/dev/null
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Type.asm" -o "$WORK/Type.sbx" >/dev/null
"$TOOL" put "$DISKS/type.img" "$WORK/Type.sbx" >/dev/null
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/More.asm" -o "$WORK/More.sbx" >/dev/null
"$TOOL" put "$DISKS/type.img" "$WORK/More.sbx" >/dev/null
# And Mode, so that the same file can be paged on both screens without a second disk. It is
# the only way to reach the forty column screen from inside a test.
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Mode.asm" -o "$WORK/Mode.sbx" >/dev/null
"$TOOL" put "$DISKS/type.img" "$WORK/Mode.sbx" >/dev/null
# A disk for Copy and Compare. The files mark all three shapes a streamed tool has to
# distinguish: no blocks, an exact whole block, and a part block. The large pair says the
# programs do not quietly depend on Data Memory being able to hold either input, and they
# differ at one byte while keeping the same length so Compare has to inspect content rather
# than stopping at the descriptor.
"$TOOL" format "$DISKS/copycompare.img" 1024 4 >/dev/null
"$TOOL" mkdir "$DISKS/copycompare.img" /Apps >/dev/null
"$TOOL" mkdir "$DISKS/copycompare.img" /Input >/dev/null
"$TOOL" mkdir "$DISKS/copycompare.img" /Output >/dev/null
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Copy.asm" -o "$WORK/Copy.sbx" >/dev/null
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Compare.asm" -o "$WORK/Compare.sbx" >/dev/null
"$TOOL" put "$DISKS/copycompare.img" "$WORK/Copy.sbx" /Apps/Copy.sbx >/dev/null
"$TOOL" put "$DISKS/copycompare.img" "$WORK/Compare.sbx" /Apps/Compare.sbx >/dev/null
: > copy-empty.dat
awk 'BEGIN { for (i = 0; i < 256; i++) printf "%c", 65 + (i % 26) }' > copy-exact.dat
awk 'BEGIN { for (i = 0; i < 257; i++) printf "%c", 65 + (i % 26) }' > copy-tail.dat
awk 'BEGIN { for (i = 0; i < 84000; i++) printf "%c", 65 + (i % 26) }' > copy-large.dat
awk 'BEGIN { for (i = 0; i < 84000; i++) printf "%c", (i == 65535 ? 33 : 65 + (i % 26)) }' \
> copy-different.dat
"$TOOL" put "$DISKS/copycompare.img" copy-empty.dat /Input/empty.dat >/dev/null
"$TOOL" put "$DISKS/copycompare.img" copy-exact.dat /Input/exact.dat >/dev/null
"$TOOL" put "$DISKS/copycompare.img" copy-tail.dat /Input/tail.dat >/dev/null
"$TOOL" put "$DISKS/copycompare.img" copy-large.dat /Input/large.dat >/dev/null
"$TOOL" put "$DISKS/copycompare.img" copy-different.dat /Input/different.dat >/dev/null
# A disk with directories on it, which is the whole of what version two adds. Built by the
# host tool, because at this rung the machine can read a tree and not yet make one - and
# that split is the point: the two implementations are checked against each other rather
# than each against itself.
#
# ONE NAME IS PUT DOWN TWICE, in two directories, because a name meaning something
# different in each place is what directories are for and what a flat filesystem cannot do.
#
# The two are DIFFERENT PROGRAMS on purpose. Say echoes whatever it is given and hello
# prints one fixed line, so which of them ran is written in the output. Two copies of one
# program would have been the easier fixture and a useless one: resolving to the wrong
# Say.sbx would have printed exactly what resolving to the right one prints, and the test
# would have passed while the path walk was ignoring directories entirely.
"$TOOL" format "$DISKS/tree.img" 256 4 >/dev/null
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Say.asm" -o "$WORK/Say.sbx" >/dev/null
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/hello.asm" -o "$WORK/treeHello.sbx" >/dev/null
"$TOOL" mkdir "$DISKS/tree.img" /Apps >/dev/null
"$TOOL" mkdir "$DISKS/tree.img" /Apps/Deep >/dev/null
"$TOOL" put "$DISKS/tree.img" "$WORK/Say.sbx" /Apps/Say.sbx >/dev/null
"$TOOL" put "$DISKS/tree.img" "$WORK/treeHello.sbx" /Apps/Deep/Say.sbx >/dev/null
printf 'this is not a program' > rooted.txt
"$TOOL" put "$DISKS/tree.img" rooted.txt >/dev/null
# A disk for the program that tries to commit a file bigger than it reserved. Its own,
# because what it leaves behind is a file whose size is the thing being checked.
"$TOOL" format "$DISKS/claim.img" 64 2 >/dev/null
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Claim.asm" -o "$WORK/Claim.sbx" >/dev/null
"$TOOL" put "$DISKS/claim.img" "$WORK/Claim.sbx" >/dev/null
# A blank disk for the machine to build a tree on itself. It starts as a version ONE disk
# with nothing at all on it, because half of what this checks is that making the first
# directory raises the version - the number says what is on a disk rather than what made
# it, so a disk with no directories is flat whoever formatted it.
"$TOOL" format "$DISKS/build.img" 128 2 >/dev/null
# A disk for moving about on. Two directories hold a file of THE SAME NAME with different
# text in it, which is the fixture the working directory needs: "notes.txt" has to mean a
# different file from each of them, and the only way to see that it does is for the two to
# say different things.
#
# It is also what checks the remembered file is dropped when the machine moves. That cache
# is keyed on the path as somebody typed it, so "notes.txt" is the same key in both places
# and nothing about the entry it remembers looks wrong - it is the kind of stale that is
# believed rather than noticed.
#
# Type and Say go in /Apps, so that a program can be started from anywhere; hello goes in
# /A under the name Say.sbx, so that where you are is visibly tried before /Apps.
"$TOOL" format "$DISKS/cwd.img" 256 4 >/dev/null
"$TOOL" mkdir "$DISKS/cwd.img" /Apps >/dev/null
"$TOOL" mkdir "$DISKS/cwd.img" /A >/dev/null
"$TOOL" mkdir "$DISKS/cwd.img" /B >/dev/null
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Type.asm" -o "$WORK/Type.sbx" >/dev/null
"$TOOL" put "$DISKS/cwd.img" "$WORK/Type.sbx" /Apps/Type.sbx >/dev/null
"$TOOL" put "$DISKS/cwd.img" "$WORK/Say.sbx" /Apps/Say.sbx >/dev/null
"$TOOL" put "$DISKS/cwd.img" "$WORK/treeHello.sbx" /A/Say.sbx >/dev/null
printf 'these are the notes in A\n' > notesA.txt
printf 'and these are the very different notes in B\n' > notesB.txt
"$TOOL" put "$DISKS/cwd.img" notesA.txt /A/notes.txt >/dev/null
"$TOOL" put "$DISKS/cwd.img" notesB.txt /B/notes.txt >/dev/null
# Wander is the only thing that can move the machine from inside a program, which makes it
# the only thing that can check the shell puts the working directory back afterwards.
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Wander.asm" -o "$WORK/Wander.sbx" >/dev/null
"$TOOL" put "$DISKS/cwd.img" "$WORK/Wander.sbx" /Apps/Wander.sbx >/dev/null
# A disk for invoking a program by typing its name. Every file on it is there to say one
# thing about how a typed word turns into a file that runs.
#
# dir.sbx is a working program under the name of a built-in command, which is the only way
# to check that the built-ins really are tried first. If the search ever moved ahead of the
# dispatch chain, this disk would start answering "dir" with a program.
#
# notes.sbx is text under a program's name, so that a file that IS found and IS NOT a
# program can be told apart from a word that names nothing. notes.txt is the same text
# under its own name - which the shell could not reach at all until it began trying the
# word as typed, because "notes.txt" only ever looked for notes.txt.sbx.
#
# Greet is a program with NO extension, and hello.sh is a script. Neither could be started
# by typing its name before the shell decided what to run by reading it, and between them
# they are the whole of that claim: SBEX loads, #! is read as lines, and the name says
# nothing about which happened. /Apps/away.sh is the same for a script found somewhere
# other than where you are standing.
"$TOOL" format "$DISKS/invoke.img" 64 2 >/dev/null
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Say.asm" -o "$WORK/Say.sbx" >/dev/null
"$TOOL" put "$DISKS/invoke.img" "$WORK/Say.sbx" >/dev/null
"$TOOL" put "$DISKS/invoke.img" "$WORK/Say.sbx" dir.sbx >/dev/null
"$TOOL" put "$DISKS/invoke.img" "$WORK/Say.sbx" Greet >/dev/null
printf 'this is not a program' > notes.txt
"$TOOL" put "$DISKS/invoke.img" notes.txt >/dev/null
"$TOOL" put "$DISKS/invoke.img" notes.txt notes.sbx >/dev/null
printf '#!\necho a script, started by its name\n' > "$WORK/hello.sh"
"$TOOL" put "$DISKS/invoke.img" "$WORK/hello.sh" hello.sh >/dev/null
"$TOOL" mkdir "$DISKS/invoke.img" /Apps >/dev/null
printf '#!\necho a script from the system place\n' > "$WORK/away.sh"
"$TOOL" put "$DISKS/invoke.img" "$WORK/away.sh" /Apps/away.sh >/dev/null
# A disk for the assembler that runs on the machine. It holds a source file and the two
# programs that check the front end - the reader on its own and the tokenizer on its own -
# because a fault in either of those would otherwise turn up much later as a mysterious
# wrong byte in an output file.
#
# hello.asm is here rather than something written for the occasion because it is the
# oldest program in the repository: the first thing this machine ever ran is the first
# thing it assembles for itself.
"$TOOL" format "$DISKS/asm.img" 2048 4 >/dev/null
cp "$ROOT/Programs/Examples/hello.asm" hello.asm
"$TOOL" put "$DISKS/asm.img" hello.asm >/dev/null
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" -I "$ROOT/Programs/CosmOS/Assembler" \
"$ROOT/Programs/CosmOS/Assembler/readTest.asm" -o "$WORK/readTest.sbx" >/dev/null
"$TOOL" put "$DISKS/asm.img" "$WORK/readTest.sbx" >/dev/null
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" -I "$ROOT/Programs/CosmOS/Assembler" \
"$ROOT/Programs/CosmOS/Assembler/tokenTest.asm" -o "$WORK/tokenTest.sbx" >/dev/null
"$TOOL" put "$DISKS/asm.img" "$WORK/tokenTest.sbx" >/dev/null
# A tree deeper than the prompt can print, for the buffer that used to walk off the front
# of itself. Eight directories of twenty two characters is 184 characters of path, and the
# prompt is built backwards into 127 bytes - so before this was bounded, the walk wrote
# down past the start of the buffer and over the shell's own command names. The word
# "exit" was the first casualty, five bytes below.
#
# MADE HERE RATHER THAN BY THE TEST, because a path this long cannot be given to mkdir in
# one piece: naming it is capped well below what walking into it a level at a time can
# reach, which is exactly why the depth is unbounded in the first place.
"$TOOL" format "$DISKS/deep.img" 512 8 >/dev/null
DEEPPATH=""
for i in 1 2 3 4 5 6 7 8; do
DEEPPATH="$DEEPPATH/abcdefghijklmnopqrst0$i"
"$TOOL" mkdir "$DISKS/deep.img" "$DEEPPATH" >/dev/null
done
# A disk whose superblock claims a directory bigger than the parent field can name. Entry
# 65535 has no parent number - index plus one wraps to zero, which is the root - so
# anything created inside it lands in the root instead while the tool reports success.
#
# THE CLAIM IS ALL THAT IS NEEDED, so this is a small disk that lies rather than a sixteen
# megabyte one that tells the truth. Mounting reads block 0 and gets as far as the
# geometry, which is where it is refused. SplitDisk will not make one of these any more,
# so the superblock is written by hand.
"$TOOL" format "$DISKS/bigdir.img" 64 2 >/dev/null
printf '\x20\x00' | dd of="$DISKS/bigdir.img" bs=1 seek=10 conv=notrunc status=none
# A disk that can be started: stage one reads the live boot slot into Program Memory and
# jumps into it, and each slot holds a different payload so that choosing between them is
# visible rather than assumed.
#
# The payload is written RAW. A loadable program carries a sixteen byte header saying where
# its pieces go, and stage one does not read headers - it reads blocks, which is the whole
# point of keeping it small enough to put in a ROM.
"$TOOL" format "$DISKS/chain.img" 256 4 4 >/dev/null
"$ROOT/Assembler" "$ROOT/Programs/Boot/slotTest.asm" -o "$WORK/slotTest.sbx" >/dev/null
tail -c +17 "$WORK/slotTest.sbx" > "$WORK/slotTest.raw"
"$TOOL" boot "$DISKS/chain.img" "$WORK/slotTest.raw" 0 >/dev/null
# The other slot says something else, so a test that reads "booted" is reading slot zero
# rather than reading whatever happens to be in Program Memory.
sed 's/INIA 0x62 ; "b"/INIA 0x6F ; "o"/; s/INIA 0x74 ; "t"/INIA 0x68 ; "h"/' \
"$ROOT/Programs/Boot/slotTest.asm" > "$WORK/otherSlot.asm"
"$ROOT/Assembler" "$WORK/otherSlot.asm" -o "$WORK/otherSlot.sbx" >/dev/null
tail -c +17 "$WORK/otherSlot.sbx" > "$WORK/otherSlot.raw"
"$TOOL" boot "$DISKS/chain.img" "$WORK/otherSlot.raw" 1 >/dev/null
# And the same disk with the other slot chosen, so both are exercised.
cp "$DISKS/chain.img" "$DISKS/chainAlt.img"
"$TOOL" bootslot "$DISKS/chainAlt.img" 1 >/dev/null
# And a slot whose payload has a Data Segment of its own. Stage one places Program Memory
# and nothing else, so the payload copies its data down from just past its own code - which
# is the mechanism the real second stage will need, since sbfs.asm has variables and a
# string it compares against.
"$TOOL" format "$DISKS/chainData.img" 256 4 4 >/dev/null
"$ROOT/Assembler" "$ROOT/Programs/Boot/slotData.asm" -o "$WORK/slotData.sbx" >/dev/null
tail -c +17 "$WORK/slotData.sbx" > "$WORK/slotData.raw"
"$TOOL" boot "$DISKS/chainData.img" "$WORK/slotData.raw" 0 >/dev/null
# ---- A disk the machine can start itself from ----
#
# Stage one in the emulator's hands, stage two in a boot slot, and the system as an
# ordinary file. Nothing here is a boot-specific format: /System/cosmos.bin is the same
# SPBT image the emulator has always been handed directly, which is what makes a program
# that wants no operating system startable the same way.
# ---- Forty blocks a slot, the same as a disk that ships ----
#
# It was thirty-two, which was ten thousand bytes of headroom when stage two was four
# thousand bytes and none at all when it reached 8,234. A fixture tighter than the thing it
# stands in for is a fixture that fails on a change the real disk would have taken, and the
# failure says "the boot slot is too small" rather than what actually grew.
"$TOOL" format "$DISKS/selfboot.img" 512 4 40 >/dev/null
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/Boot/stage2.asm" -o "$WORK/stage2.sbx" >/dev/null
tail -c +17 "$WORK/stage2.sbx" > "$WORK/stage2.raw"
"$TOOL" boot "$DISKS/selfboot.img" "$WORK/stage2.raw" 0 >/dev/null
"$ROOT/Assembler" -I "$ROOT/Programs/Libraries" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Source/cosmos.asm" -o "$WORK/bootCosmos.bin" >/dev/null
"$ROOT/Assembler" "$ROOT/Programs/Boot/bare.asm" -o "$WORK/bare.bin" >/dev/null
"$TOOL" mkdir "$DISKS/selfboot.img" /System >/dev/null
"$TOOL" mkdir "$DISKS/selfboot.img" /System/Boot >/dev/null
"$TOOL" put "$DISKS/selfboot.img" "$WORK/bootCosmos.bin" /System/Boot/cosmos.bin >/dev/null
"$TOOL" put "$DISKS/selfboot.img" "$WORK/bare.bin" /System/Boot/bare.bin >/dev/null
"$TOOL" mkdir "$DISKS/selfboot.img" /Apps >/dev/null
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Files.asm" -o "$WORK/bootFiles.sbx" >/dev/null
"$TOOL" put "$DISKS/selfboot.img" "$WORK/bootFiles.sbx" /Apps/Files.sbx >/dev/null
# And one with no system on it, so that a second stage which cannot find what to start
# says so rather than jumping somewhere.
"$TOOL" format "$DISKS/nosystem.img" 512 4 40 >/dev/null
"$TOOL" boot "$DISKS/nosystem.img" "$WORK/stage2.raw" 0 >/dev/null
# ---- Configuration choosing what starts ----
#
# The same disk three ways, differing only in /System/Boot/boot.cfg. Nothing else about
# any of them changes, which is what makes these tests of the FILE rather than of the
# machinery under it.
for variant in bare broken fallback; do
cp "$DISKS/selfboot.img" "$DISKS/cfg$variant.img"
done
# A bare metal image, which is the whole point of loading an ordinary boot image: a program
# wanting no operating system is a file like any other.
printf 'system /System/Boot/bare.bin\n' > "$WORK/bare.cfg"
"$TOOL" put "$DISKS/cfgbare.img" "$WORK/bare.cfg" /System/Boot/boot.cfg >/dev/null
# Every kind of mistake at once, and a good setting underneath them. The machine still
# starts, and says what it could not use on the way.
{
printf '; a file with things wrong in it\n'
printf 'fallbcak /System/Boot/cosmos.bin\n'
printf 'system\n'
printf 'system /System/Boot/bare.bin\n'
} > "$WORK/broken.cfg"
"$TOOL" put "$DISKS/cfgbroken.img" "$WORK/broken.cfg" /System/Boot/boot.cfg >/dev/null
# A system that is not there, and something to fall back to.
{
printf 'system /System/Boot/missing.bin\n'
printf 'fallback /System/Boot/cosmos.bin\n'
} > "$WORK/fallback.cfg"
"$TOOL" put "$DISKS/cfgfallback.img" "$WORK/fallback.cfg" /System/Boot/boot.cfg >/dev/null
# ---- A system that never reaches a prompt ----
#
# The case the boot state exists for. Pointing the configuration at something that dies
# before the shell is, without a mark on the disk, a machine that can never be told
# anything again - so the loader marks it before handing over and the system clears the
# mark on arrival, which makes "did not arrive" a thing the next start can see.
"$ROOT/Assembler" "$ROOT/Programs/Boot/wedged.asm" -o "$WORK/wedged.bin" >/dev/null
for stage in first second third; do
cp "$DISKS/selfboot.img" "$DISKS/wedge$stage.img"
"$TOOL" put "$DISKS/wedge$stage.img" "$WORK/wedged.bin" /System/Boot/wedged.bin >/dev/null
{
printf 'system /System/Boot/wedged.bin\n'
printf 'fallback /System/Boot/cosmos.bin\n'
} > "$WORK/wedge.cfg"
"$TOOL" put "$DISKS/wedge$stage.img" "$WORK/wedge.cfg" /System/Boot/boot.cfg >/dev/null
done
# Each disk begins where the one before it ended, so the three tests read as three
# consecutive starts of one machine without any of them depending on the others running.
"$TOOL" bootstate "$DISKS/wedgesecond.img" 1 >/dev/null
"$TOOL" bootstate "$DISKS/wedgethird.img" 2 >/dev/null
# ---- Settling from the machine itself ----
#
# The loop the boot state opens has to be closeable from inside: a machine that says
# "settle it to try again" and gives you no way to do so has told you about a problem it
# will not let you fix. Settle is a PROGRAM rather than a shell word, which is the shape
# this system is growing into - one job each, reached through SWI, replaceable.
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Settle.asm" -o "$WORK/Settle.sbx" >/dev/null
cp "$DISKS/wedgethird.img" "$DISKS/settle.img"
"$TOOL" put "$DISKS/settle.img" "$WORK/Settle.sbx" /Apps/Settle.sbx >/dev/null
# And one already settled, so that saying so is checked as well as doing it.
cp "$DISKS/selfboot.img" "$DISKS/settled.img"
"$TOOL" put "$DISKS/settled.img" "$WORK/Settle.sbx" /Apps/Settle.sbx >/dev/null
# A configuration naming something broken with NOTHING to fall back to. The mark would
# otherwise be the only thing between the machine and its own configuration, and refusing
# on the strength of it turns "the last start failed" into "no start is permitted".
cp "$DISKS/selfboot.img" "$DISKS/nofallback.img"
"$TOOL" put "$DISKS/nofallback.img" "$WORK/wedged.bin" /System/Boot/wedged.bin >/dev/null
printf 'system /System/Boot/cosmos.bin\n' > "$WORK/lonely.cfg"
"$TOOL" put "$DISKS/nofallback.img" "$WORK/lonely.cfg" /System/Boot/boot.cfg >/dev/null
"$TOOL" bootstate "$DISKS/nofallback.img" 1 >/dev/null
# ---- A program that stops while a note is still held ----
#
# Its own disk rather than a place on cosmos.img, because everything on that one appears in a
# directory listing and ten recorded tests quote those. A fixture that exists to be run by one
# check should not move ten others every time it changes size.
"$TOOL" format "$DISKS/quiet.img" 128 2 >/dev/null
"$TOOL" mkdir "$DISKS/quiet.img" /Apps >/dev/null
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/testPrograms/humTest.asm" -o "$WORK/Hum.sbx" >/dev/null
"$TOOL" put "$DISKS/quiet.img" "$WORK/Hum.sbx" /Apps/Hum.sbx >/dev/null
# And something silent to run after it, because the machine stops the moment the shell runs
# out of input - a note quietened at that instant leaves no samples behind to say whether it
# was quietened at all.
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/testPrograms/pauseTest.asm" -o "$WORK/Pause.sbx" >/dev/null
"$TOOL" put "$DISKS/quiet.img" "$WORK/Pause.sbx" /Apps/Pause.sbx >/dev/null
# ---- A tune in a file, and the player that reads one ----
#
# maketune.py lays the bytes out by hand. It is NOT the compiler: the sequences and the
# patches are written as literal bytes and the only thing computed is where each piece lands,
# which is what makes it a fixture that tests the loader rather than one that tests itself.
#
# On this disk rather than cosmos.img for the usual reason - everything there shows in a
# directory listing that ten recorded tests quote.
python3 "$ROOT/Tests/maketune.py" "$WORK/two.tune" >/dev/null
"$TOOL" put "$DISKS/quiet.img" "$WORK/two.tune" two.tune >/dev/null
# ---- And the same tune, written down and compiled ----
#
# TWO IMPLEMENTATIONS OF ONE FORMAT, which is the discipline SplitDisk and sbfs.asm are held
# to: maketune.py lays the bytes out by hand and TuneC compiles a written source, and the
# suite checks that they agree byte for byte. Either one alone would only be self-consistent.
#
# The patches are SoundPatch's, in the raw form TuneC embeds. Two blobs differing in one
# parameter, the octave, so the same note number sounds an octave apart.
"$ROOT/SoundPatch" --blob "$ROOT/Programs/Sounds/Oboe.json" Low "$WORK/low.patch" >/dev/null
python3 - "$WORK/low.patch" "$WORK/high.patch" <<'FIXTURE'
# The fixture patches are written here rather than converted, because what they have to be is
# two patches identical but for the octave - which is a fact about the test and not about any
# instrument anybody designed.
import sys
def blob(octave):
pairs = [(0x00, 2), (0x01, 0xFF), (0x05, 1), (0x20, 0),
(0x21, 0), (0x22, 0xFF), (0x23, 5), (0x04, octave)]
return bytes([len(pairs)]) + b"".join(bytes(p) for p in pairs)
open(sys.argv[1], "wb").write(blob(128))
open(sys.argv[2], "wb").write(blob(129))
FIXTURE
"$ROOT/TuneC" -I "$WORK" "$ROOT/Tests/two.score" "$WORK/compiled.tune" >/dev/null
# The same tune at half the speed. A TICK BELONGS TO THE TUNE, and the only reason nothing
# noticed that Play was ignoring it was that every fixture asked for the tick Play happened to
# have written into itself. One that asks for something else is the whole check.
sed 's/#Tick 0d125000/#Tick 0d250000/' "$ROOT/Tests/two.score" > "$WORK/slow.score"
"$ROOT/TuneC" -I "$WORK" "$WORK/slow.score" "$WORK/slow.tune" >/dev/null
"$TOOL" put "$DISKS/quiet.img" "$WORK/slow.tune" slow.tune >/dev/null
"$ROOT/Assembler" -I "$ROOT/Programs/Sounds" -I "$ROOT/Programs/Libraries" \
-I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Play.asm" -o "$WORK/Play.sbx" >/dev/null
"$TOOL" put "$DISKS/quiet.img" "$WORK/Play.sbx" /Apps/Play.sbx >/dev/null
# ---- What a program made of it ----
#
# A status is only worth having if it survives the program that set it, so this runs three
# in a row and asks after each: one that works, one that is asked wrongly, and one that
# fails outright. Status is a PROGRAM and reaches the number through a service, which is
# what will let something other than a person read it.
"$TOOL" format "$DISKS/status.img" 512 4 >/dev/null
"$TOOL" mkdir "$DISKS/status.img" /Apps >/dev/null
for app in Status Say Type Settle; do
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/$app.asm" -o "$WORK/$app.sbx" >/dev/null
"$TOOL" put "$DISKS/status.img" "$WORK/$app.sbx" /Apps/$app.sbx >/dev/null
done
# ---- A file with lines longer than the editor's buffer ----
#
# Edit copied a file into an 81 byte line buffer with NO BOUND, and the buffer is followed
# in memory by the head of the document and the pointer its allocator hands out. A source
# file with 94 character lines wrote characters over both: a 31 line file opened as 3, and
# opening it a second time walked a list that led back into itself for ever.
#
# Typing was always safe - osReadLine is told how much room there is - so a new document
# behaved and a source file did not, which is exactly how it was found.
"$TOOL" format "$DISKS/editlong.img" 512 4 >/dev/null
"$TOOL" mkdir "$DISKS/editlong.img" /Apps >/dev/null
for app in Edit Status; do
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/$app.asm" -o "$WORK/$app.sbx" >/dev/null
"$TOOL" put "$DISKS/editlong.img" "$WORK/$app.sbx" /Apps/$app.sbx >/dev/null
done
"$TOOL" put "$DISKS/editlong.img" "$ROOT/Programs/CosmOS/Apps/hello.asm" /hello.asm >/dev/null
# And one past what it can hold, which must be refused rather than shortened: a line this
# cut would be written back cut, and the file damaged by having been looked at.
{ printf 'short line\n'; printf 'x%.0s' $(seq 1 200); printf '\nanother short one\n'; } \
> "$WORK/toolong.txt"
"$TOOL" put "$DISKS/editlong.img" "$WORK/toolong.txt" /long.txt >/dev/null
# ---- Starting something else just this once ----
#
# A program that owns the whole machine has nowhere to run: it cannot be started from the
# shell, because starting it means there is no shell, and pointing boot.cfg at it means a
# machine that keeps starting it. Once writes a request the loader reads before boot.cfg
# and deletes before it jumps.
#
# Three disks, each one start further along, so the three tests read as three consecutive
# starts of one machine without any of them depending on another having run.
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Once.asm" -o "$WORK/Once.sbx" >/dev/null
cp "$DISKS/selfboot.img" "$DISKS/onceasked.img"
"$TOOL" put "$DISKS/onceasked.img" "$WORK/Once.sbx" /Apps/Once.sbx >/dev/null
# The request, written by hand so that the second and third disks do not depend on the
# first test having run to make it.
printf 'system /System/Boot/bare.bin\n' > "$WORK/once.cfg"
cp "$DISKS/onceasked.img" "$DISKS/oncedue.img"
"$TOOL" put "$DISKS/oncedue.img" "$WORK/once.cfg" /System/Boot/once.cfg >/dev/null
# And after it has been taken: the request is gone, and nothing else changed.
cp "$DISKS/onceasked.img" "$DISKS/onceafter.img"
# ---- Starting again without leaving the machine ----
#
# The whole loop in one session: ask for a one shot, restart, watch it own the machine.
# Before Reboot the only way to restart was to stop the emulator and run it again, which
# meant the one thing the machine could not do was the thing Once was written for.
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Reboot.asm" -o "$WORK/Reboot.sbx" >/dev/null
cp "$DISKS/onceasked.img" "$DISKS/reboot.img"
"$TOOL" put "$DISKS/reboot.img" "$WORK/Reboot.sbx" /Apps/Reboot.sbx >/dev/null