|
- # E07-S05: drive build/n128c.prg through scripts/launch-compiler.ps1 on a fresh
- # development image and check the PRG, MAP, ERR and summary it produces.
- #
- # success COUNTER.N8 -> COUNTER.PRG + COUNTER.MAP, no ERR, summary on screen
- # failure BAD.N8 -> BAD.ERR, no BAD.PRG/BAD.MAP, earlier outputs intact
- # collision COUNTER.N8 -> OUTPUT EXISTS, COUNTER.PRG byte-identical
- # run the generated COUNTER.PRG reports MB_ST_PASS in a second session
- [CmdletBinding()]
- param([int]$Port = 6552, [int]$TimeoutSeconds = 240)
-
- $ErrorActionPreference = "Stop"
- $project = Split-Path $PSScriptRoot -Parent
- Import-Module (Join-Path $PSScriptRoot "lib\Nova128Test.psm1") -Force
- & (Join-Path $PSScriptRoot "build-driver.ps1") | Out-Host
-
- $root = Join-Path $project "build\driver-test"
- if (Test-Path -LiteralPath $root) { Remove-Item -LiteralPath $root -Recurse -Force }
- New-Item -ItemType Directory -Force -Path $root | Out-Null
- $driver = Join-Path $project "build\n128c.prg"
- $work = Join-Path $root "work.d81"
- $c1541 = Get-ViceTool "c1541"
- $failures = @()
-
- # 0: prepare-disk.ps1 injects exactly the build the driver was made from.
- $prepared = Join-Path $root "prepared.d81"
- & (Join-Path $PSScriptRoot "prepare-disk.ps1") -OutputImage $prepared `
- -CompilerPrg $driver -RequireCompiler | Out-Host
- $injectedPath = Join-Path $root "prepared-n128c"
- & $c1541 $prepared -read "n128c" $injectedPath 2>&1 | Out-Null
- if (-not (Test-Path -LiteralPath $injectedPath)) {
- $failures += "prepare-disk.ps1 did not inject n128c"
- } elseif ([Convert]::ToBase64String([IO.File]::ReadAllBytes($injectedPath)) -ne
- [Convert]::ToBase64String([IO.File]::ReadAllBytes($driver))) {
- $failures += "prepare-disk.ps1 injected a different n128c"
- }
-
- $counterPet = Join-Path $root "counter.pet"
- [IO.File]::WriteAllBytes($counterPet, (ConvertTo-Nova128Petscii `
- (Get-Content -Raw -LiteralPath (Join-Path $project "tests\compiler\counter.n8"))))
- $badPet = Join-Path $root "bad.pet"
- [IO.File]::WriteAllBytes($badPet, (ConvertTo-Nova128Petscii `
- (Get-Content -Raw -LiteralPath (Join-Path $project "tests\grammar\rejected\miss_end.n8"))))
- $helloPet = Join-Path $root "hello.pet"
- [IO.File]::WriteAllBytes($helloPet, (ConvertTo-Nova128Petscii `
- (Get-Content -Raw -LiteralPath (Join-Path $project "tests\compiler\hello.n8"))))
- New-Nova128Disk -Path $work -Files @(
- @{ Name = "n128c"; Path = $driver; Type = "prg" },
- @{ Name = "counter.n8"; Path = $counterPet; Type = "seq" },
- @{ Name = "bad.n8"; Path = $badPet; Type = "seq" },
- @{ Name = "hello.n8"; Path = $helloPet; Type = "seq" })
-
- function Invoke-Compile {
- param([string]$Keys, [string]$Screenshot)
- if (Test-Path -LiteralPath $Screenshot) { Remove-Item -LiteralPath $Screenshot -Force }
- & (Join-Path $PSScriptRoot "launch-compiler.ps1") -CompilerPrg $driver -DiskImage $work `
- -SkipPrepare -Keys $Keys -KeyDelay 4000 -LimitCycles 200000000 `
- -Screenshot $Screenshot -Warp | Out-Host
- if (-not (Test-Path -LiteralPath $Screenshot)) {
- throw "the compiler session produced no exit screenshot"
- }
- }
-
- function Get-Listing {
- return ((& $c1541 -attach $work -list 2>&1) -join "`n")
- }
-
- function Read-DiskFile {
- param([string]$Name, [string]$Type, [string]$Dest)
- if (Test-Path -LiteralPath $Dest) { Remove-Item -LiteralPath $Dest -Force }
- & $c1541 $work -read "$Name,$Type" $Dest 2>&1 | Out-Null
- if (-not (Test-Path -LiteralPath $Dest)) { throw "could not read $Name,$Type from the disk" }
- return ,([IO.File]::ReadAllBytes($Dest))
- }
-
- function Get-ScreenText {
- param($Session)
- $ram = Read-Nova128Memory $Session 0x0400 1000
- $rows = for ($r = 0; $r -lt 25; $r++) {
- $chars = for ($c = 0; $c -lt 40; $c++) {
- # Do not mask bit 7: a set bit is reverse video, which means the
- # program left the editor's RVS flag ($F3) on (D157). Such cells
- # decode to '?' so the literal search fails.
- $code = $ram[$r * 40 + $c]
- if ($code -le 0x1F) { [char]($code + 0x40) } elseif ($code -le 0x3F) { [char]$code } else { '?' }
- }
- -join $chars
- }
- return ($rows -join "`n")
- }
-
- # 1: the on-screen summary, read from a copy so the three launcher runs below
- # still start from an image without outputs.
- $summaryDisk = Join-Path $root "summary.d81"
- Copy-Item -LiteralPath $work -Destination $summaryDisk -Force
- $session = Start-Nova128X128 -Autostart $summaryDisk -Drive8Image $summaryDisk -Port $Port `
- -Keys "counter.n8\n" -KeyDelay 4000 -Screenshot (Join-Path $root "summary-screen.png")
- try {
- $screenText = ""
- $deadline = [DateTime]::UtcNow.AddSeconds(120)
- while ([DateTime]::UtcNow -lt $deadline) {
- Invoke-Nova128Monitor $session "x" | Out-Null
- Start-Sleep -Seconds 1
- $screenText = Get-ScreenText $session
- if ($screenText -match "OUTPUT COUNTER\.PRG" -and $screenText -match "0 ERRORS" -and
- $screenText -match "SOURCE COUNTER\.N8") { break }
- }
- } finally {
- Stop-Nova128X128 $session
- }
- foreach ($needle in @("NOVA128 COMPILER", "SOURCE COUNTER.N8", "OUTPUT COUNTER.PRG", "0 ERRORS")) {
- if ($screenText -notmatch [regex]::Escape($needle)) {
- $failures += "the summary did not show '$needle'"
- }
- }
-
- # 2: success through the launcher.
- Invoke-Compile -Keys "counter.n8\n" -Screenshot (Join-Path $root "counter-summary.png")
- $listing = Get-Listing
- if ($listing -notmatch '"counter\.prg"\s+prg') { $failures += "COUNTER.PRG is missing" }
- if ($listing -notmatch '"counter\.map"\s+seq') { $failures += "COUNTER.MAP is missing" }
- if ($listing -match '"counter\.err"') { $failures += "a clean source left COUNTER.ERR" }
-
- $prgPath = Join-Path $root "counter.prg"
- $prg = Read-DiskFile -Name "counter.prg" -Type "p" -Dest $prgPath
- $stub = [byte[]](0x0B, 0x1C, 0x0A, 0x00, 0x9E, 0x37, 0x31, 0x38, 0x31, 0x00, 0x00, 0x00)
- if ($prg.Length -lt 14 -or $prg[0] -ne 0x01 -or $prg[1] -ne 0x1C) {
- $failures += "COUNTER.PRG does not load at `$1C01"
- } else {
- for ($i = 0; $i -lt $stub.Length; $i++) {
- if ($prg[2 + $i] -ne $stub[$i]) {
- $failures += ("COUNTER.PRG stub byte {0} is {1:X2}, expected {2:X2}" -f $i, $prg[2 + $i], $stub[$i])
- break
- }
- }
- }
-
- # c1541 translates PETSCII CR to CRLF on read, so normalize before matching.
- $map = ([Text.Encoding]::ASCII.GetString((Read-DiskFile -Name "counter.map" -Type "s" `
- -Dest (Join-Path $root "counter.map"))) -replace "`r", "`n")
- $mapLines = $map -split "`n"
- if ($mapLines[0] -ne "NOVA128 MAP 1") { $failures += "MAP header is '$($mapLines[0])'" }
- if ($map -notmatch '(?im)^ENTRY 1c0d') { $failures += "MAP ENTRY is not 1C0D" }
- $sections = @{}
- foreach ($m in [regex]::Matches($map, '(?im)^SECTION (\w+) ([0-9a-f]{4}) ([0-9a-f]{4})')) {
- $sections[$m.Groups[1].Value.ToUpper()] = @{
- Start = [Convert]::ToInt32($m.Groups[2].Value, 16)
- Size = [Convert]::ToInt32($m.Groups[3].Value, 16)
- }
- }
- foreach ($name in @("STUB", "ENTRY", "START", "PRINT", "CODE")) {
- if (-not $sections.ContainsKey($name)) { $failures += "MAP has no $name section" }
- }
- $ordered = @("ENTRY", "START", "PRINT", "CODE")
- if ($sections.ContainsKey("DATA")) { $ordered += "DATA" }
- if ($sections.ContainsKey("STUB") -and
- ($sections.STUB.Start -ne 0x1C01 -or $sections.STUB.Size -ne 0x0C)) {
- $failures += "MAP STUB section is wrong"
- }
- $expectedStart = 0x1C0D
- foreach ($name in $ordered) {
- if (-not $sections.ContainsKey($name)) { continue }
- if ($sections[$name].Start -ne $expectedStart) {
- $failures += ("{0} starts at {1:X4}, expected {2:X4}" -f $name, $sections[$name].Start, $expectedStart)
- }
- if ($sections[$name].Size -le 0) { $failures += "$name section is empty" }
- $expectedStart += $sections[$name].Size
- }
- if ($sections.ContainsKey("CODE")) {
- $expectedLen = 2 + $sections.STUB.Size
- foreach ($name in $ordered) { $expectedLen += $sections[$name].Size }
- if ($prg.Length -ne $expectedLen) {
- $failures += ("PRG is {0} bytes, MAP accounts for {1}" -f $prg.Length, $expectedLen)
- }
- }
-
- # 3: a syntax error writes the ERR file and no PRG, and leaves COUNTER.PRG alone.
- Invoke-Compile -Keys "bad.n8\n" -Screenshot (Join-Path $root "bad-summary.png")
- $listing = Get-Listing
- if ($listing -notmatch '"bad\.err"\s+seq') { $failures += "BAD.ERR is missing" }
- if ($listing -match '"bad\.prg"') { $failures += "a failing source produced BAD.PRG" }
- if ($listing -match '"bad\.map"') { $failures += "a failing source produced BAD.MAP" }
- $errText = [Text.Encoding]::ASCII.GetString((Read-DiskFile -Name "bad.err" -Type "s" `
- -Dest (Join-Path $root "bad.err")))
- if ($errText -notmatch '^E\d{3} BAD\.N8 \d+ ') { $failures += "BAD.ERR has no catalogued diagnostic" }
- if ($listing -notmatch '"counter\.prg"\s+prg') { $failures += "the failure removed COUNTER.PRG" }
- $afterFailure = Read-DiskFile -Name "counter.prg" -Type "p" -Dest (Join-Path $root "counter-after.prg")
- if ([Convert]::ToBase64String($afterFailure) -ne [Convert]::ToBase64String($prg)) {
- $failures += "the failing run changed COUNTER.PRG"
- }
-
- # 4: an existing output is refused and the file stays byte-identical.
- Invoke-Compile -Keys "counter.n8\n" -Screenshot (Join-Path $root "collision-summary.png")
- $listing = Get-Listing
- if (([regex]::Matches($listing, '"counter\.prg"')).Count -ne 1) {
- $failures += "the collision run changed the COUNTER.PRG directory entry"
- }
- $afterCollision = Read-DiskFile -Name "counter.prg" -Type "p" -Dest (Join-Path $root "counter-again.prg")
- if ([Convert]::ToBase64String($afterCollision) -ne [Convert]::ToBase64String($prg)) {
- $failures += "a refused output changed COUNTER.PRG"
- }
-
- # 5: the generated program itself runs and reaches its started/pass contract.
- $runSession = Start-Nova128X128 -Autostart $prgPath -Port ($Port + 1) `
- -Screenshot (Join-Path $root "counter-run.png")
- try {
- $mb = Wait-Nova128Mailbox $runSession -TimeoutSeconds 120
- } finally {
- Stop-Nova128X128 $runSession
- }
- if ($mb[0] -ne 0x4E -or $mb[1] -ne 0x38) { $failures += "the generated program did not initialise the mailbox" }
- if ($mb[2] -ne 0x03) { $failures += ("producer {0:X2}, expected 03 (generated program)" -f $mb[2]) }
- if ($mb[3] -ne 0xA5) { $failures += ("generated program status {0:X2}, expected A5" -f $mb[3]) }
-
- # 6: a print source compiles with the copied runtime and prints on screen.
- Invoke-Compile -Keys "hello.n8\n" -Screenshot (Join-Path $root "hello-summary.png")
- $listing = Get-Listing
- if ($listing -notmatch '"hello\.prg"\s+prg') { $failures += "HELLO.PRG is missing" }
- if ($listing -match '"hello\.err"') { $failures += "HELLO.N8 left HELLO.ERR" }
- $helloMap = ([Text.Encoding]::ASCII.GetString((Read-DiskFile -Name "hello.map" -Type "s" `
- -Dest (Join-Path $root "hello.map"))) -replace "`r", "`n")
- if ($helloMap -notmatch '(?im)^SECTION PRINT ') { $failures += "HELLO.MAP has no PRINT section" }
- $helloPrgPath = Join-Path $root "hello.prg"
- $helloBytes = Read-DiskFile -Name "hello.prg" -Type "p" -Dest $helloPrgPath
- $helloSections = @{}
- foreach ($m in [regex]::Matches($helloMap, '(?im)^SECTION (\w+) ([0-9a-f]{4}) ([0-9a-f]{4})')) {
- $helloSections[$m.Groups[1].Value.ToUpper()] = @{
- Start = [Convert]::ToInt32($m.Groups[2].Value, 16)
- Size = [Convert]::ToInt32($m.Groups[3].Value, 16)
- }
- }
- if (-not $helloSections.ContainsKey("DATA")) {
- $failures += "HELLO.MAP has no DATA section"
- } else {
- $literal = [Text.Encoding]::ASCII.GetBytes("HELLO FROM NOVA128")
- $dataOffset = 2 + $helloSections.STUB.Size + $helloSections.ENTRY.Size +
- $helloSections.START.Size + $helloSections.PRINT.Size + $helloSections.CODE.Size
- if ($helloBytes.Length -lt $dataOffset + $literal.Length) {
- $failures += "HELLO.PRG is shorter than its DATA section"
- } else {
- for ($i = 0; $i -lt $literal.Length; $i++) {
- if ($helloBytes[$dataOffset + $i] -ne $literal[$i]) {
- $failures += "HELLO.PRG DATA does not hold the print literal"
- break
- }
- }
- }
- $codeOffset = 2 + $helloSections.STUB.Size + $helloSections.ENTRY.Size +
- $helloSections.START.Size + $helloSections.PRINT.Size
- $calledPrint = $false
- for ($i = 0; $i -lt $helloSections.CODE.Size - 2; $i++) {
- if ($helloBytes[$codeOffset + $i] -eq 0x20) {
- $target = $helloBytes[$codeOffset + $i + 1] + 256 * $helloBytes[$codeOffset + $i + 2]
- if ($target -ge $helloSections.PRINT.Start -and
- $target -lt ($helloSections.PRINT.Start + $helloSections.PRINT.Size)) {
- $calledPrint = $true
- break
- }
- }
- }
- if (-not $calledPrint) { $failures += "HELLO.PRG CODE never calls the PRINT runtime" }
- }
- $helloSession = Start-Nova128X128 -Autostart $helloPrgPath -Port ($Port + 2) `
- -Screenshot (Join-Path $root "hello-run.png")
- try {
- $helloMb = Wait-Nova128Mailbox $helloSession -TimeoutSeconds 60
- $helloText = Get-ScreenText $helloSession
- } finally {
- Stop-Nova128X128 $helloSession
- }
- if ($helloMb[3] -ne 0xA5) { $failures += "HELLO.PRG did not return from its body" }
- if ($helloText -notmatch "HELLO FROM NOVA128") { $failures += "HELLO.PRG did not print its string" }
-
- if ($failures) { throw ("n128c verification failed:`n " + ($failures -join "`n ")) }
- Write-Host ("summary: SOURCE COUNTER.N8 / OUTPUT COUNTER.PRG / 0 ERRORS")
- Write-Host ("success: COUNTER.PRG {0} bytes, COUNTER.MAP {1} bytes, no COUNTER.ERR" -f
- $prg.Length, $map.Length)
- Write-Host ("failure: BAD.ERR {0}, no BAD.PRG, COUNTER.PRG unchanged" -f $errText.Trim())
- Write-Host "injection: prepare-disk.ps1 wrote build/n128c.prg byte-identically as n128c"
- Write-Host "collision: OUTPUT EXISTS and COUNTER.PRG unchanged"
- Write-Host "run: generated COUNTER.PRG reported MB_ST_PASS in x128"
- Write-Host "print: HELLO.PRG carries PRINT+DATA, calls the runtime, and shows 'HELLO FROM NOVA128' on screen"
|