#!/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 # Where.sbx says where it was loaded from, and where a name would be beside it. TWO COPIES # on purpose, which is the whole test: the one at the root is found where you are standing # and is named by the bare word that was typed, so the system has to work out what that word # meant; the one in /Apps is found by a path that already says where it is. Both have to come # out saying the same kind of thing, and the /Apps one has to go on saying it from a # directory that has no copy of its own. "$ROOT/Assembler" -I "$ROOT/Programs/Libraries" -I "$ROOT/Programs/CosmOS/Source" \ "$ROOT/Programs/CosmOS/Apps/Where.asm" -o "$WORK/Where.sbx" >/dev/null "$TOOL" put "$DISKS/cosmos.img" "$WORK/Where.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 # ---- Lunar Porter, which is the first application with a directory of its own ---- # # Lander.sbx is 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. # # It does NOT go in /Apps any more. A program with assets gets /Packages/app. and a # LAUNCHER in /Apps under the bare name that is typed - so what "Lander" names is a script, # which the shell runs because of what is inside it rather than what it is called. Every # recorded Lander test still types just "Lander" and knows nothing about any of this, which # is exactly the claim being made. "$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" mkdir "$DISKS/cosmos.img" /Packages >/dev/null "$TOOL" mkdir "$DISKS/cosmos.img" /Packages/app.Lander >/dev/null "$TOOL" put "$DISKS/cosmos.img" "$WORK/Lander.sbx" /Packages/app.Lander/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. # # BESIDE THE GAME rather than at the root, which is what the game now asks for: it is told # where it was loaded from and joins the tune's name to that. /lander.state stays at the root # on purpose - a saved position is the PERSON's, so it is found where the person is standing, # and the two of them together are the whole distinction between an asset and a document. "$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" /Packages/app.Lander/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 "$TOOL" put "$DISKS/cosmos.img" "$WORK/Where.sbx" /Apps/Where.sbx >/dev/null # And the launcher for Lunar Porter, under the bare name somebody types. Put here rather than # beside the game above, because /Apps is not made until now. "$TOOL" put "$DISKS/cosmos.img" "$ROOT/Programs/CosmOS/Launchers/Lander" /Apps/Lander >/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