o recorder

- digest tv support added to recorder/playback module
This commit is contained in:
steve 2019-04-30 21:03:03 +01:00
parent 99d04c747b
commit 218c3d7823
10 changed files with 291 additions and 157 deletions

View file

@ -64,4 +64,5 @@ const (
// Recorder
RecordingError
PlaybackError
PlaybackHashError
)

View file

@ -61,6 +61,7 @@ var messages = map[Errno]string{
UnknownPeripheralEvent: "this peripheral (%s) does not understand that event (%v)",
// Recorder
RecordingError: "error when recording input (%s)",
PlaybackError: "error when playing back recorded input (%s)",
RecordingError: "error when recording input (%s)",
PlaybackError: "error when playing back recorded input (%s)",
PlaybackHashError: "hash error when playing back recording input (%s)",
}

View file

@ -115,7 +115,7 @@ func main() {
fallthrough
case "PLAY":
tvMode := modeFlags.String("tv", "NTSC", "television specification: NTSC, PAL")
tvType := modeFlags.String("tv", "NTSC", "television specification: NTSC, PAL")
scaling := modeFlags.Float64("scale", 3.0, "television scaling")
stable := modeFlags.Bool("stable", true, "wait for stable frame before opening display")
record := modeFlags.Bool("record", false, "record user input to a file")
@ -124,12 +124,15 @@ func main() {
switch len(modeFlags.Args()) {
case 0:
fmt.Println("* 2600 cartridge required")
os.Exit(2)
if *recording == "" {
fmt.Println("* 2600 cartridge required")
os.Exit(2)
}
fallthrough
case 1:
err := playmode.Play(modeFlags.Arg(0), *tvMode, float32(*scaling), *stable, *recording, *record)
err := playmode.Play(modeFlags.Arg(0), *tvType, float32(*scaling), *stable, *recording, *record)
if err != nil {
fmt.Printf("* error running emulator: %s\n", err)
fmt.Println(err)
os.Exit(2)
}
default:
@ -204,7 +207,7 @@ func main() {
case "FPS":
display := modeFlags.Bool("display", false, "display TV output: boolean")
tvMode := modeFlags.String("tv", "NTSC", "television specification: NTSC, PAL")
tvType := modeFlags.String("tv", "NTSC", "television specification: NTSC, PAL")
scaling := modeFlags.Float64("scale", 3.0, "television scaling")
runTime := modeFlags.String("time", "5s", "run duration (note: there is a 2s overhead)")
profile := modeFlags.Bool("profile", false, "perform cpu and memory profiling")
@ -215,7 +218,7 @@ func main() {
fmt.Println("* 2600 cartridge required")
os.Exit(2)
case 1:
err := fps(*profile, modeFlags.Arg(0), *display, *tvMode, float32(*scaling), *runTime)
err := fps(*profile, modeFlags.Arg(0), *display, *tvType, float32(*scaling), *runTime)
if err != nil {
fmt.Printf("* error starting fps profiler: %s\n", err)
os.Exit(2)
@ -275,7 +278,7 @@ func main() {
}
case "ADD":
tvMode := modeFlags.String("tv", "NTSC", "television specification: NTSC, PAL")
tvType := modeFlags.String("tv", "NTSC", "television specification: NTSC, PAL")
numFrames := modeFlags.Int("frames", 10, "number of frames to run")
modeFlagsParse()
@ -284,7 +287,7 @@ func main() {
fmt.Println("* 2600 cartridge required")
os.Exit(2)
case 1:
err := regression.RegressAddCartridge(modeFlags.Arg(0), *tvMode, *numFrames)
err := regression.RegressAddCartridge(modeFlags.Arg(0), *tvType, *numFrames)
if err != nil {
fmt.Printf("* error adding regression test: %s\n", err)
os.Exit(2)
@ -295,7 +298,7 @@ func main() {
os.Exit(2)
}
case "UPDATE":
tvMode := modeFlags.String("tv", "NTSC", "television specification: NTSC, PAL")
tvType := modeFlags.String("tv", "NTSC", "television specification: NTSC, PAL")
numFrames := modeFlags.Int("frames", 10, "number of frames to run")
modeFlagsParse()
@ -304,7 +307,7 @@ func main() {
fmt.Println("* 2600 cartridge required")
os.Exit(2)
case 1:
err := regression.RegressUpdateCartridge(modeFlags.Arg(0), *tvMode, *numFrames)
err := regression.RegressUpdateCartridge(modeFlags.Arg(0), *tvType, *numFrames)
if err != nil {
fmt.Printf("* error updating regression test: %s\n", err)
os.Exit(2)
@ -318,12 +321,12 @@ func main() {
}
}
func fps(profile bool, cartridgeFile string, display bool, tvMode string, scaling float32, runTime string) error {
func fps(profile bool, cartridgeFile string, display bool, tvType string, scaling float32, runTime string) error {
var fpstv television.Television
var err error
if display {
fpstv, err = sdl.NewGUI(tvMode, scaling, nil)
fpstv, err = sdl.NewGUI(tvType, scaling, nil)
if err != nil {
return fmt.Errorf("error preparing television: %s", err)
}

View file

@ -1,6 +1,7 @@
package memory
import (
"crypto/sha1"
"fmt"
"gopher2600/errors"
"io"
@ -20,7 +21,14 @@ type Cartridge struct {
Area
AreaInfo
name string
// full path to the cartridge as stored on disk
Filename string
// hash of binary loaded from disk. any subsequent pokes to cartridge
// memory will not be reflected in the value
Hash string
// cartridge bank-switching method
method string
NumBanks int
@ -40,6 +48,7 @@ type Cartridge struct {
}
const ejectedName = "ejected"
const ejectedHash = "nohash"
const ejectedMethod = "none"
// NewCart is the preferred method of initialisation for the cartridges
@ -56,12 +65,12 @@ func NewCart() *Cartridge {
// MachineInfoTerse returns the cartridge information in terse format
func (cart Cartridge) MachineInfoTerse() string {
return fmt.Sprintf("%s [%s] bank=%d", cart.name, cart.method, cart.Bank)
return fmt.Sprintf("%s [%s] bank=%d", cart.Filename, cart.method, cart.Bank)
}
// MachineInfo returns the cartridge information in verbose format
func (cart Cartridge) MachineInfo() string {
return fmt.Sprintf("name: %s\nmethod: %s\nbank:%d", cart.name, cart.method, cart.Bank)
return fmt.Sprintf("name: %s\nmethod: %s\nbank:%d", cart.Filename, cart.method, cart.Bank)
}
// Label is an implementation of Area.Label
@ -146,6 +155,14 @@ func (cart *Cartridge) Attach(filename string) error {
return err
}
// generate hash
key := sha1.New()
if _, err := io.Copy(key, cf); err != nil {
return err
}
cart.Hash = fmt.Sprintf("%x", key.Sum(nil))
// we always start in bank 0
cart.Bank = 0
// set default read hooks
@ -354,7 +371,7 @@ func (cart *Cartridge) Attach(filename string) error {
}
// note name of cartridge
cart.name = filename
cart.Filename = filename
return nil
}
@ -418,8 +435,9 @@ func (cart *Cartridge) BankSwitch(bank int) error {
// Eject removes memory from cartridge space and unlike the real hardware,
// attaches a bank of empty memory - for convenience of the debugger
func (cart *Cartridge) Eject() {
cart.name = ejectedName
cart.Filename = ejectedName
cart.method = ejectedMethod
cart.Hash = ejectedHash
cart.NumBanks = 1
cart.Bank = 0
cart.readBanks(nil, cart.NumBanks)

View file

@ -9,13 +9,12 @@ import (
"gopher2600/recorder"
"path"
"strings"
"sync/atomic"
"time"
)
// Play sets the emulation running - without any debugging features
func Play(cartridgeFile, tvMode string, scaling float32, stable bool, recording string, newRecording bool) error {
playtv, err := sdl.NewGUI(tvMode, scaling, nil)
func Play(cartridgeFile, tvType string, scaling float32, stable bool, recording string, newRecording bool) error {
playtv, err := sdl.NewGUI(tvType, scaling, nil)
if err != nil {
return fmt.Errorf("error preparing television: %s", err)
}
@ -31,11 +30,6 @@ func Play(cartridgeFile, tvMode string, scaling float32, stable bool, recording
}
vcs.Ports.Player0.Attach(stk)
err = vcs.AttachCartridge(cartridgeFile)
if err != nil {
return err
}
// create default recording file name if no name has been supplied
if newRecording && recording == "" {
shortCartName := path.Base(cartridgeFile)
@ -45,35 +39,67 @@ func Play(cartridgeFile, tvMode string, scaling float32, stable bool, recording
recording = fmt.Sprintf("recording_%s_%s", shortCartName, timestamp)
}
var rec *recorder.Recorder
var plb *recorder.Playback
// note that we attach the cartridge in three different branches below - we
// need to do this at different times depending on whether a new recording
// or playback is taking place; or if it's just a regular playback
if recording != "" {
if newRecording {
recording, err := recorder.NewRecorder(recording, vcs)
err = vcs.AttachCartridge(cartridgeFile)
if err != nil {
return fmt.Errorf("error preparing VCS: %s", err)
return err
}
rec, err = recorder.NewRecorder(recording, vcs)
if err != nil {
return fmt.Errorf("error preparing recording: %s", err)
}
defer func() {
recording.End()
rec.End()
}()
vcs.Ports.Player0.AttachTranscriber(recording)
vcs.Ports.Player1.AttachTranscriber(recording)
vcs.Panel.AttachTranscriber(recording)
vcs.Ports.Player0.AttachTranscriber(rec)
vcs.Ports.Player1.AttachTranscriber(rec)
vcs.Panel.AttachTranscriber(rec)
} else {
recording, err := recorder.NewPlayback(recording, vcs)
plb, err = recorder.NewPlayback(recording, vcs)
if err != nil {
return fmt.Errorf("error preparing VCS: %s", err)
return fmt.Errorf("error playing back recording: %s", err)
}
vcs.Ports.Player0.Attach(recording)
vcs.Ports.Player1.Attach(recording)
vcs.Panel.Attach(recording)
vcs.Ports.Player0.Attach(plb)
vcs.Ports.Player1.Attach(plb)
vcs.Panel.Attach(plb)
if cartridgeFile != "" && cartridgeFile != plb.CartName {
return fmt.Errorf("error playing back recording: cartridge name doesn't match the name in the recording")
}
// if no cartridge filename has been provided then use the one in
// the playback file
cartridgeFile = plb.CartName
err = vcs.AttachCartridge(cartridgeFile)
if err != nil {
return err
}
}
} else {
err = vcs.AttachCartridge(cartridgeFile)
if err != nil {
return err
}
}
// run while value of running variable is positive
var running atomic.Value
running.Store(0)
// now that we've attached the cartridge check the hash against the
// playback has (if it exists)
if plb != nil && plb.CartHash != vcs.Mem.Cart.Hash {
return fmt.Errorf("error playing back recording: cartridge hash doesn't match")
}
// connect debugger to gui
guiChannel := make(chan gui.Event, 2)
@ -90,6 +116,7 @@ func Play(cartridgeFile, tvMode string, scaling float32, stable bool, recording
}
// run and handle gui events
return vcs.Run(func() (bool, error) {
select {
case ev := <-guiChannel:

74
recorder/fileformat.go Normal file
View file

@ -0,0 +1,74 @@
package recorder
import (
"fmt"
"gopher2600/errors"
"io"
"strings"
)
const (
fieldID int = iota
fieldEvent
fieldFrame
fieldScanline
fieldHorizPos
fieldHash
numFields
)
const fieldSep = ", "
// playback file header format
// ---------------------------
//
// # <cartridge name>
// # <cartridge hash>
// # <tv type>
const (
lineCartName int = iota
lineCartHash
lineTVtype
numHeaderLines
)
func (rec *Recorder) writeHeader() error {
lines := make([]string, numHeaderLines)
// add header information
lines[lineCartName] = rec.vcs.Mem.Cart.Filename
lines[lineCartHash] = rec.vcs.Mem.Cart.Hash
lines[lineTVtype] = fmt.Sprintf("%v\n", rec.vcs.TV.GetSpec().ID)
line := strings.Join(lines, "\n")
n, err := io.WriteString(rec.output, line)
if err != nil {
rec.output.Close()
return errors.NewFormattedError(errors.RecordingError, err)
}
if n != len(line) {
rec.output.Close()
return errors.NewFormattedError(errors.RecordingError, "output truncated")
}
return nil
}
func (plb *Playback) readHeader(lines []string) error {
// read header
plb.CartName = lines[lineCartName]
plb.CartHash = lines[lineCartHash]
plb.TVtype = lines[lineTVtype]
// validate header
tvspec := plb.vcs.TV.GetSpec()
if tvspec.ID != lines[lineTVtype] {
return errors.NewFormattedError(errors.PlaybackError, "current TV type does not match that in the recording")
}
return nil
}

View file

@ -6,6 +6,7 @@ import (
"gopher2600/hardware"
"gopher2600/hardware/peripherals"
"gopher2600/television"
"gopher2600/television/renderers"
"io/ioutil"
"os"
"strconv"
@ -17,6 +18,10 @@ type event struct {
frame int
scanline int
horizpos int
hash string
// the line in the recording file the playback event appears
line int
}
type playbackSequence struct {
@ -27,12 +32,19 @@ type playbackSequence struct {
// Playback is an implementation of the controller interface. it reads from an
// existing recording file and responds to GetInput() requests
type Playback struct {
CartName string
CartHash string
TVtype string
vcs *hardware.VCS
digest *renderers.DigestTV
sequences map[string]*playbackSequence
}
// NewPlayback is hte preferred method of implementation for the Playback type
func NewPlayback(transcript string, vcs *hardware.VCS) (*Playback, error) {
var err error
// check we're working with correct information
if vcs == nil || vcs.TV == nil {
return nil, errors.NewFormattedError(errors.PlaybackError, "no playback hardware available")
@ -41,73 +53,83 @@ func NewPlayback(transcript string, vcs *hardware.VCS) (*Playback, error) {
plb := &Playback{vcs: vcs}
plb.sequences = make(map[string]*playbackSequence)
// open file
// create digesttv, piggybacking on the tv already being used by vcs
plb.digest, err = renderers.NewDigestTV(vcs.TV.GetSpec().ID, vcs.TV)
if err != nil {
return nil, errors.NewFormattedError(errors.RecordingError, err)
}
// open file; read the entirity of the contents; close file
tf, err := os.Open(transcript)
if err != nil {
return nil, errors.NewFormattedError(errors.PlaybackError, err)
}
buffer, err := ioutil.ReadAll(tf)
if err != nil {
return nil, errors.NewFormattedError(errors.PlaybackError, err)
}
err = tf.Close()
if err != nil {
return nil, errors.NewFormattedError(errors.PlaybackError, err)
}
_ = tf.Close()
// convert buffer to an array of lines
// convert file contents to an array of lines
lines := strings.Split(string(buffer), "\n")
// read header
tvspec := plb.vcs.TV.GetSpec()
if tvspec.ID != lines[0] {
return nil, errors.NewFormattedError(errors.PlaybackError, "current TV type does not match that in the recording")
// read header and perform validation checks
err = plb.readHeader(lines)
if err != nil {
return nil, err
}
// loop through transcript and divide events according to the first field
// (the ID)
for i := 1; i < len(lines)-1; i++ {
for i := numHeaderLines; i < len(lines)-1; i++ {
toks := strings.Split(lines[i], fieldSep)
// ignore lines that don't have enough fields
if len(toks) != numFields {
continue
msg := fmt.Sprintf("expected %d fields at line %d", numFields, i+1)
return nil, errors.NewFormattedError(errors.PlaybackError, msg)
}
// add a new playbackSequence for the id if it doesn't exist
id := toks[0]
id := toks[fieldID]
if _, ok := plb.sequences[id]; !ok {
plb.sequences[id] = &playbackSequence{}
}
// create a new event and convert tokens accordingly
// any errors in the transcript causes failure
event := event{}
event := event{line: i + 1}
n, err := strconv.Atoi(toks[1])
n, err := strconv.Atoi(toks[fieldEvent])
if err != nil {
msg := fmt.Sprintf("line %d, col %d", i+1, len(strings.Join(toks[:2], fieldSep)))
msg := fmt.Sprintf("%s line %d, col %d", err, i+1, len(strings.Join(toks[:2], fieldSep)))
return nil, errors.NewFormattedError(errors.PlaybackError, msg)
}
event.event = peripherals.Event(n)
event.frame, err = strconv.Atoi(toks[2])
event.frame, err = strconv.Atoi(toks[fieldFrame])
if err != nil {
msg := fmt.Sprintf("line %d, col %d", i+1, len(strings.Join(toks[:3], fieldSep)))
msg := fmt.Sprintf("%s line %d, col %d", err, i+1, len(strings.Join(toks[:3], fieldSep)))
return nil, errors.NewFormattedError(errors.PlaybackError, msg)
}
event.scanline, err = strconv.Atoi(toks[3])
event.scanline, err = strconv.Atoi(toks[fieldScanline])
if err != nil {
msg := fmt.Sprintf("line %d, col %d", i+1, len(strings.Join(toks[:4], fieldSep)))
msg := fmt.Sprintf("%s line %d, col %d", err, i+1, len(strings.Join(toks[:4], fieldSep)))
return nil, errors.NewFormattedError(errors.PlaybackError, msg)
}
event.horizpos, err = strconv.Atoi(toks[4])
event.horizpos, err = strconv.Atoi(toks[fieldHorizPos])
if err != nil {
msg := fmt.Sprintf("line %d, col %d", i+1, len(strings.Join(toks[:5], fieldSep)))
msg := fmt.Sprintf("%s line %d, col %d", err, i+1, len(strings.Join(toks[:5], fieldSep)))
return nil, errors.NewFormattedError(errors.PlaybackError, msg)
}
event.hash = toks[fieldHash]
// add new event to list of events in the correct playback sequence
seq := plb.sequences[id]
seq.events = append(seq.events, event)
@ -146,6 +168,11 @@ func (plb *Playback) GetInput(id string) (peripherals.Event, error) {
// compare current state with the state in the transcript
nextEvent := seq.events[seq.eventCt]
if frame == nextEvent.frame && scanline == nextEvent.scanline && horizpos == nextEvent.horizpos {
if nextEvent.hash != plb.digest.String() {
msg := fmt.Sprintf("line %d", nextEvent.line)
return peripherals.NoEvent, errors.NewFormattedError(errors.PlaybackHashError, msg)
}
seq.eventCt++
return nextEvent.event, nil
}

View file

@ -6,32 +6,39 @@ import (
"gopher2600/hardware"
"gopher2600/hardware/peripherals"
"gopher2600/television"
"gopher2600/television/renderers"
"io"
"os"
)
const fieldSep = ", "
const numFields = 5
// Recorder records controller events to disk, intended for future playback
type Recorder struct {
vcs *hardware.VCS
digest *renderers.DigestTV
output *os.File
}
// NewRecorder is the preferred method of implementation for the FileRecorder type
func NewRecorder(transcript string, vcs *hardware.VCS) (*Recorder, error) {
var err error
// check we're working with correct information
if vcs == nil || vcs.TV == nil {
return nil, errors.NewFormattedError(errors.RecordingError, "hardware is not suitable for recording")
}
scr := &Recorder{vcs: vcs}
rec := &Recorder{vcs: vcs}
// create digesttv, piggybacking on the tv already being used by vcs
rec.digest, err = renderers.NewDigestTV(vcs.TV.GetSpec().ID, vcs.TV)
if err != nil {
return nil, errors.NewFormattedError(errors.RecordingError, err)
}
// open file
_, err := os.Stat(transcript)
_, err = os.Stat(transcript)
if os.IsNotExist(err) {
scr.output, err = os.Create(transcript)
rec.output, err = os.Create(transcript)
if err != nil {
return nil, errors.NewFormattedError(errors.RecordingError, "can't create file")
}
@ -39,26 +46,17 @@ func NewRecorder(transcript string, vcs *hardware.VCS) (*Recorder, error) {
return nil, errors.NewFormattedError(errors.RecordingError, "file already exists")
}
// add header information
tvspec := scr.vcs.TV.GetSpec()
line := fmt.Sprintf("%v\n", tvspec.ID)
n, err := io.WriteString(scr.output, line)
err = rec.writeHeader()
if err != nil {
scr.output.Close()
return nil, errors.NewFormattedError(errors.RecordingError, err)
}
if n != len(line) {
scr.output.Close()
return nil, errors.NewFormattedError(errors.RecordingError, "output truncated")
return nil, err
}
return scr, nil
return rec, nil
}
// End closes the output file.
func (scr *Recorder) End() error {
err := scr.output.Close()
func (rec *Recorder) End() error {
err := rec.output.Close()
if err != nil {
return errors.NewFormattedError(errors.RecordingError, err)
}
@ -67,38 +65,49 @@ func (scr *Recorder) End() error {
}
// Transcribe implements the Transcriber interface
func (scr *Recorder) Transcribe(id string, event peripherals.Event) error {
func (rec *Recorder) Transcribe(id string, event peripherals.Event) error {
// don't do anything if event is the NoEvent
if event == peripherals.NoEvent {
return nil
}
// sanity checks
if scr.output == nil {
if rec.output == nil {
return errors.NewFormattedError(errors.RecordingError, "recording file is not open")
}
if scr.vcs == nil || scr.vcs.TV == nil {
if rec.vcs == nil || rec.vcs.TV == nil {
return errors.NewFormattedError(errors.RecordingError, "hardware is not suitable for recording")
}
// create line and write to file
frame, err := scr.vcs.TV.GetState(television.ReqFramenum)
frame, err := rec.vcs.TV.GetState(television.ReqFramenum)
if err != nil {
return err
}
scanline, err := scr.vcs.TV.GetState(television.ReqScanline)
scanline, err := rec.vcs.TV.GetState(television.ReqScanline)
if err != nil {
return err
}
horizpos, err := scr.vcs.TV.GetState(television.ReqHorizPos)
horizpos, err := rec.vcs.TV.GetState(television.ReqHorizPos)
if err != nil {
return err
}
line := fmt.Sprintf("%v%s%v%s%v%s%v%s%v\n", id, fieldSep, event, fieldSep, frame, fieldSep, scanline, fieldSep, horizpos)
line := fmt.Sprintf("%v%s%v%s%v%s%v%s%v%s%v\n", id,
fieldSep,
event,
fieldSep,
frame,
fieldSep,
scanline,
fieldSep,
horizpos,
fieldSep,
rec.digest.String(),
)
n, err := io.WriteString(scr.output, line)
n, err := io.WriteString(rec.output, line)
if err != nil {
return errors.NewFormattedError(errors.RecordingError, err)
}

View file

@ -1,7 +1,6 @@
package regression
import (
"crypto/sha1"
"encoding/csv"
"fmt"
"gopher2600/errors"
@ -12,29 +11,15 @@ import (
const regressionDBFile = ".gopher2600/regressionDB"
func getCartridgeHash(cartridgeFile string) (string, error) {
f, err := os.Open(cartridgeFile)
if err != nil {
return "", err
}
defer f.Close()
key := sha1.New()
if _, err := io.Copy(key, f); err != nil {
return "", err
}
return fmt.Sprintf("%x", key.Sum(nil)), nil
}
type regressionEntry struct {
cartridgeHash string
cartridgePath string
tvMode string
numOFrames int
screenDigest string
}
const numFields = 4
func (entry regressionEntry) String() string {
return fmt.Sprintf("%s [%s] frames=%d", entry.cartridgePath, entry.tvMode, entry.numOFrames)
}
@ -62,36 +47,37 @@ func startSession() (*regressionDB, error) {
return db, nil
}
func (db *regressionDB) endSession() error {
func (db *regressionDB) endSession(commitChanges bool) error {
// write entries to regression database
csvw := csv.NewWriter(db.dbfile)
if commitChanges {
csvw := csv.NewWriter(db.dbfile)
err := db.dbfile.Truncate(0)
if err != nil {
return err
}
db.dbfile.Seek(0, os.SEEK_SET)
for _, entry := range db.entries {
rec := make([]string, 5)
rec[0] = entry.cartridgeHash
rec[1] = entry.cartridgePath
rec[2] = entry.tvMode
rec[3] = strconv.Itoa(entry.numOFrames)
rec[4] = entry.screenDigest
err := csvw.Write(rec)
err := db.dbfile.Truncate(0)
if err != nil {
return err
}
}
// make sure everything's been written
csvw.Flush()
err = csvw.Error()
if err != nil {
return err
db.dbfile.Seek(0, os.SEEK_SET)
for _, entry := range db.entries {
rec := make([]string, numFields)
rec[0] = entry.cartridgePath
rec[1] = entry.tvMode
rec[2] = strconv.Itoa(entry.numOFrames)
rec[3] = entry.screenDigest
err := csvw.Write(rec)
if err != nil {
return err
}
}
// make sure everything's been written
csvw.Flush()
err = csvw.Error()
if err != nil {
return err
}
}
// end session by closing file
@ -114,7 +100,7 @@ func (db *regressionDB) readEntries() error {
csvr.Comment = rune('#')
csvr.TrimLeadingSpace = true
csvr.ReuseRecord = true
csvr.FieldsPerRecord = 5
csvr.FieldsPerRecord = numFields
db.dbfile.Seek(0, os.SEEK_SET)
@ -128,20 +114,19 @@ func (db *regressionDB) readEntries() error {
return err
}
numOfFrames, err := strconv.Atoi(rec[3])
numOfFrames, err := strconv.Atoi(rec[2])
if err != nil {
return err
}
// add entry to database
entry := regressionEntry{
cartridgeHash: rec[0],
cartridgePath: rec[1],
tvMode: rec[2],
cartridgePath: rec[0],
tvMode: rec[1],
numOFrames: numOfFrames,
screenDigest: rec[4]}
screenDigest: rec[3]}
db.entries[entry.cartridgeHash] = entry
db.entries[entry.cartridgePath] = entry
}
return nil
@ -153,7 +138,7 @@ func addCartridge(cartridgeFile string, tvMode string, numOfFrames int, allowUpd
if err != nil {
return err
}
defer db.endSession()
defer db.endSession(true)
// run cartdrige and get digest
digest, err := run(cartridgeFile, tvMode, numOfFrames)
@ -161,20 +146,14 @@ func addCartridge(cartridgeFile string, tvMode string, numOfFrames int, allowUpd
return err
}
// add new entry to database
key, err := getCartridgeHash(cartridgeFile)
if err != nil {
return err
}
entry := regressionEntry{
cartridgeHash: key,
cartridgePath: cartridgeFile,
tvMode: tvMode,
numOFrames: numOfFrames,
screenDigest: digest}
if allowUpdate == false {
if existEntry, ok := db.entries[entry.cartridgeHash]; ok {
if existEntry, ok := db.entries[entry.cartridgePath]; ok {
if existEntry.cartridgePath == entry.cartridgePath {
return errors.NewFormattedError(errors.RegressionEntryExists, entry)
}
@ -183,7 +162,7 @@ func addCartridge(cartridgeFile string, tvMode string, numOfFrames int, allowUpd
}
}
db.entries[entry.cartridgeHash] = entry
db.entries[entry.cartridgePath] = entry
return nil
}

View file

@ -14,18 +14,13 @@ func RegressDeleteCartridge(cartridgeFile string) error {
if err != nil {
return err
}
defer db.endSession()
defer db.endSession(true)
key, err := getCartridgeHash(cartridgeFile)
if err != nil {
return err
}
if _, ok := db.entries[key]; ok == false {
if _, ok := db.entries[cartridgeFile]; ok == false {
return errors.NewFormattedError(errors.RegressionEntryDoesNotExist, cartridgeFile)
}
delete(db.entries, key)
delete(db.entries, cartridgeFile)
return nil
}
@ -46,7 +41,7 @@ func RegressRunTests(output io.Writer, failOnError bool) (int, int, error) {
if err != nil {
return -1, -1, err
}
defer db.endSession()
defer db.endSession(false)
numSucceed := 0
numFail := 0