Skip to content

Commit

Permalink
Add timeout flag, for testing. (#188)
Browse files Browse the repository at this point in the history
* Add timeout flag, for testing.

This will now be used for all test-cases, and will cap runs at two
minutes.  This might be useful to others, but for the moment I struggle
to think of a good purpose!

This closes #186.

* Setup a default context for when nothing was specified
  • Loading branch information
skx authored Jan 25, 2025
1 parent 1d2e54b commit a75e805
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 3 deletions.
39 changes: 39 additions & 0 deletions cpm/cpm.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ var (
// It should be handled and expected by callers.
ErrBoot = errors.New("BOOT")

// ErrTimeout is used when a timeout occurs
ErrTimeout = errors.New("TIMEOUT")

// ErrUnimplemented will be used to handle a CP/M binary calling an unimplemented syscall.
//
// It should be handled and expected by callers.
Expand Down Expand Up @@ -110,6 +113,9 @@ type FileCache struct {
// CPM is the object that holds our emulator state.
type CPM struct {

// context for handling timeout
context context.Context

// syscallErr holds any error created by a BIOS or BDOS syscall handler.
//
// We need this because the handlers are invoked by our OUT-wrapper and they
Expand Down Expand Up @@ -274,6 +280,14 @@ func WithHostExec(prefix string) cpmoption {
}
}

// WithContext allows a context to be passed to the evaluator.
func WithContext(ctx context.Context) cpmoption {
return func(c *CPM) error {
c.context = ctx
return nil
}
}

// New returns a new emulation object. We support default options,
// and new defaults may be specified via WithOutputDriver, etc, etc.
func New(options ...cpmoption) (*CPM, error) {
Expand Down Expand Up @@ -573,6 +587,7 @@ func New(options ...cpmoption) (*CPM, error) {
tmp := &CPM{
BDOSSyscalls: bdos,
BIOSSyscalls: bios,
context: context.Background(),
ccp: "ccp", // default
dma: DefaultDMAAddress,
drives: make(map[string]string),
Expand Down Expand Up @@ -893,6 +908,17 @@ func (cpm *CPM) Execute(args []string) error {
}

for {
//
// Check on our timeout since we're here.
//
select {
case <-cpm.context.Done():
return ErrTimeout

default:
// nop
}

// Reset the state of any saved error and the halt-flag.
cpm.syscallErr = nil
cpm.CPU.HALT = false
Expand All @@ -908,6 +934,10 @@ func (cpm *CPM) Execute(args []string) error {
return ErrHalt
}

if err == ErrTimeout || cpm.syscallErr == ErrTimeout {
return ErrTimeout
}

// An unimplemented system-call was encountered.
// That's a fatal-error
if err == ErrUnimplemented || cpm.syscallErr == ErrUnimplemented {
Expand Down Expand Up @@ -1013,6 +1043,15 @@ func (cpm *CPM) In(addr uint8) uint8 {
// A register and there are far far fewer of them.
func (cpm *CPM) Out(addr uint8, val uint8) {

select {
case <-cpm.context.Done():
cpm.CPU.HALT = true
cpm.syscallErr = ErrTimeout
return
default:
// nop
}

if cpm.CPU.HALT {
slog.Error("Out() called when CPU is halted",
slog.Group("Out",
Expand Down
25 changes: 23 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
package main

import (
"context"
"flag"
"fmt"
"log/slog"
"os"
"slices"
"sort"
"strings"
"time"

cpmccp "github.com/skx/cpmulator/ccp"
"github.com/skx/cpmulator/consolein"
Expand Down Expand Up @@ -64,6 +66,7 @@ func main() {
logPath := flag.String("log-path", "", "Specify the file to write debug logs to.")
prnPath := flag.String("prn-path", "print.log", "Specify the file to write printer-output to.")
showVersion := flag.Bool("version", false, "Report our version, and exit.")
timeout := flag.Int("timeout", 0, "Timeout execution after the given number of seconds")
useDirectories := flag.Bool("directories", false, "Use subdirectories on the host computer for CP/M drives.")

// listing
Expand Down Expand Up @@ -214,6 +217,13 @@ func main() {
defer logFile.Close()
}

ctx := context.Background()
var cancel context.CancelFunc
if *timeout != 0 {
ctx, cancel = context.WithTimeout(context.Background(), time.Duration(*timeout)*time.Second)
defer cancel()
}

// Create our logging handler, using the level we've just setup.
log = slog.New(
slog.NewJSONHandler(
Expand All @@ -230,6 +240,7 @@ func main() {
cpm.WithOutputDriver(*output),
cpm.WithInputDriver(*input),
cpm.WithHostExec(*execPrefix),
cpm.WithContext(ctx),
cpm.WithCCP(*ccp))
if err != nil {
fmt.Printf("error creating CPM object: %s\n", err)
Expand Down Expand Up @@ -342,13 +353,18 @@ func main() {

// Deliberate stop of execution
if err == cpm.ErrHalt {
fmt.Printf("\n")
fmt.Printf("\r\n")
return
}

// Reboot attempt, also fine
if err == cpm.ErrBoot {
fmt.Printf("\n")
fmt.Printf("\r\n")
return
}

if err == cpm.ErrTimeout {
fmt.Printf("\r\nThe timeout of %d seconds was exceeded. Terminating!\r\n", *timeout)
return
}

Expand Down Expand Up @@ -402,6 +418,11 @@ func main() {
return
}

if err == cpm.ErrTimeout {
fmt.Printf("\r\nThe timeout of %d seconds was exceeded. Terminating!\r\n", *timeout)
return
}

fmt.Printf("\nError running CCP: %s\n", err)
return
}
Expand Down
2 changes: 2 additions & 0 deletions test/ccp.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
A:
EXIT
5 changes: 5 additions & 0 deletions test/ccp.pat
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Console input:file
Console output:adm-3a
BIOS:0xFE00
BDOS:0xFA00
CCP:ccp
2 changes: 1 addition & 1 deletion test/run-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ fi
#
export INPUT_FILE="${input}"
echo " Starting $(date)"
./cpmulator -input file -cd ../cpm-dist/ -directories -ccp ccp | ansifilter &> "$output"
./cpmulator -input file -cd ../cpm-dist/ -directories -timeout 120 -ccp ccp | ansifilter &> "$output"
echo " Completed $(date)"

#
Expand Down

0 comments on commit a75e805

Please sign in to comment.