"do build.sh cosmos.asm" looked for a file called "build.sh cosmos.asm", because scriptOpen copied the whole rest of the line into the name. So a script could be told nothing, and a launcher - a script whose entire job is to hand on what it was told - could not exist. Now the name is cut off the front and what follows is kept whole. $1 to $9 are the words of it, walked out on demand, and $args is all of them. Nothing is stored per parameter, so there is no limit on how many a script may be handed and no second number to keep in step. Both ways of starting a script pass them on: "do" and typing the name. A word that was not given is an error that stops the script, like every other name this shell does not know. Expanding it to nothing would let a command run with an argument missing and then report success, which is what stop-on-failure exists to prevent. $args is always set inside a script, empty if it was given nothing, so "if same $args" can be asked. ---- And the count that describes the block was already wrong ---- Found while adding a field to it. The state one script keeps for another is saved by a single copy of a fixed number of bytes, and that number was 71 against a block of 77: six bytes of line position had been added in the middle of it years after the count was written. So the tail of every saved script was never saved, and #quiet in a helper stayed behind in the script that called it - the opposite of what this file's own comment promises and the README documents. The unsaved line position turned out not to matter, because a loop keeps its own copy in the block record. Nothing said so. Three numbers describe this block and all three now say so in a comment, and cosmosScriptNest ends on a helper that goes quiet and a caller that must not stay that way. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
890 lines
53 KiB
Bash
Executable File
890 lines
53 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
|
|
# ---- And what a nested script gives back ----
|
|
#
|
|
# hush.script goes quiet and heard.script must not stay that way. The state one script keeps
|
|
# for another is saved by a single copy of a fixed number of bytes, and that number was six
|
|
# short of the block it was copying - so the last fields of it, quiet among them, were never
|
|
# saved at all. Nothing said so: the echo simply stopped, in the caller, after the helper
|
|
# returned. The check is that "echo aloud again" is ECHOED and not merely obeyed.
|
|
#
|
|
# MIND WHAT A NEW NAME HERE BEGINS WITH. Tab is checked against this disk, and a second file
|
|
# sharing a prefix with one of the words those tests type turns a unique completion into a
|
|
# list. "heard.script" was the first draft and it shadowed hello.sbx.
|
|
printf '#! script\n#quiet\necho the helper is quiet\n' > hush.script
|
|
"$TOOL" put "$DISKS/cosmos.img" hush.script >/dev/null
|
|
printf '#! script\necho aloud first\ndo hush.script\necho aloud again\n' > aloud.script
|
|
"$TOOL" put "$DISKS/cosmos.img" aloud.script >/dev/null
|
|
|
|
# ---- What a script is given ----
|
|
#
|
|
# args.script says what reached it; pass.script is the shape a launcher has, handing on what
|
|
# it was told without knowing what any of it means; holds.script gives a helper different
|
|
# words and must still have its own afterwards.
|
|
printf '#! script\necho one is $1\necho two is $2\necho all of it is $args\n' > args.script
|
|
"$TOOL" put "$DISKS/cosmos.img" args.script >/dev/null
|
|
printf '#! script\nSay $args\n' > pass.script
|
|
"$TOOL" put "$DISKS/cosmos.img" pass.script >/dev/null
|
|
printf '#! script\necho outer has $1\ndo args.script deep down\necho outer still has $1 and $args\n' > holds.script
|
|
"$TOOL" put "$DISKS/cosmos.img" holds.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
|