Snapshot testing for screen rendering
@ -45,6 +45,16 @@ func (k *keyboard) putKeyAction(keyEvent *fyne.KeyEvent, press bool) {
|
||||
}
|
||||
}
|
||||
|
||||
if press && ctrl {
|
||||
// F keys with ctrl do not generate events in putKey()
|
||||
switch keyEvent.Name {
|
||||
case fyne.KeyF1:
|
||||
k.s.a.SendCommand(izapple2.CommandReset)
|
||||
case fyne.KeyF12:
|
||||
screen.AddScenario(k.s.a, "../screen/test_resources/")
|
||||
}
|
||||
}
|
||||
|
||||
switch keyEvent.Name {
|
||||
case desktop.KeyControlLeft:
|
||||
k.controlLeft = press
|
||||
@ -62,7 +72,10 @@ func (k *keyboard) putKey(keyEvent *fyne.KeyEvent) {
|
||||
20 PRINT A, A - 128
|
||||
30 GOTO 10
|
||||
*/
|
||||
ctrl := k.controlLeft || k.controlRight
|
||||
|
||||
// Keys with control are not generating events in putKey()
|
||||
//ctrl := k.controlLeft || k.controlRight
|
||||
|
||||
result := uint8(0)
|
||||
switch keyEvent.Name {
|
||||
case fyne.KeyEscape:
|
||||
@ -94,15 +107,11 @@ func (k *keyboard) putKey(keyEvent *fyne.KeyEvent) {
|
||||
|
||||
// Control of the emulator
|
||||
case fyne.KeyF1:
|
||||
if ctrl {
|
||||
/*if ctrl {
|
||||
k.s.a.SendCommand(izapple2.CommandReset)
|
||||
}
|
||||
}*/
|
||||
case fyne.KeyF5:
|
||||
if ctrl {
|
||||
k.s.a.SendCommand(izapple2.CommandShowSpeed)
|
||||
} else {
|
||||
k.s.a.SendCommand(izapple2.CommandToggleSpeed)
|
||||
}
|
||||
k.s.a.SendCommand(izapple2.CommandShowSpeed)
|
||||
case fyne.KeyF6:
|
||||
if k.s.screenMode != screen.ScreenModeGreen {
|
||||
k.s.screenMode = screen.ScreenModeGreen
|
||||
|
65
screen/screen_test.go
Normal file
@ -0,0 +1,65 @@
|
||||
package screen
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSnapshots(t *testing.T) {
|
||||
// Verifies all the scenarios on the ./test_resources folder
|
||||
files, err := ioutil.ReadDir("./test_resources/")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, fileInfo := range files {
|
||||
if !fileInfo.IsDir() &&
|
||||
strings.HasSuffix(fileInfo.Name(), ".json") {
|
||||
testScenario(t, "./test_resources/"+fileInfo.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testScenario(t *testing.T, fileName string) {
|
||||
ts, err := loadTestScenario(fileName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ts.generateSnapshots(fileName, true)
|
||||
|
||||
for _, screenMode := range ts.ScreenModes {
|
||||
referenceName := buildImageName(fileName, screenMode, false)
|
||||
actualName := buildImageName(fileName, screenMode, true)
|
||||
|
||||
reference, err := ioutil.ReadFile(referenceName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
actual, err := ioutil.ReadFile(actualName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if bytes.Compare(reference, actual) != 0 {
|
||||
t.Errorf("Files %s and %s should be equal", referenceName, actualName)
|
||||
replaceIfNeeded(referenceName, actualName)
|
||||
} else {
|
||||
os.Remove(actualName)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func replaceIfNeeded(referenceName string, actualName string) {
|
||||
// If the "update" argument is passed to test. The new images replace the old.
|
||||
// Run the tests with: "go test . -args update"
|
||||
for _, arg := range os.Args {
|
||||
if arg == "update" {
|
||||
os.Remove(referenceName)
|
||||
os.Rename(actualName, referenceName)
|
||||
}
|
||||
}
|
||||
}
|
@ -65,7 +65,7 @@ func VideoModeName(vs VideoSource) string {
|
||||
case VideoMono560:
|
||||
name = "Mono560"
|
||||
case VideoRGBMix:
|
||||
name = "RGMMIX"
|
||||
name = "RGBMIX"
|
||||
case VideoRGB160:
|
||||
name = "RGB160"
|
||||
case VideoSHR:
|
||||
|
178
screen/testScenarios.go
Normal file
@ -0,0 +1,178 @@
|
||||
package screen
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"image/png"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// TestScenario is the computer video state
|
||||
type TestScenario struct {
|
||||
VideoMode uint16 `json:"mode"`
|
||||
VideoModeName string `json:"name"`
|
||||
ScreenModes []int `json:"screens"`
|
||||
TextPages [4][]uint8 `json:"text"`
|
||||
VideoPages [4][]uint8 `json:"video"`
|
||||
SVideoPage []uint8 `json:"svideo"`
|
||||
}
|
||||
|
||||
func grabTestScenario(vs VideoSource) *TestScenario {
|
||||
textPages := [4][]uint8{
|
||||
cloneSlice(vs.GetTextMemory(false, false)),
|
||||
cloneSlice(vs.GetTextMemory(false, true)),
|
||||
cloneSlice(vs.GetTextMemory(true, false)),
|
||||
cloneSlice(vs.GetTextMemory(true, true)),
|
||||
}
|
||||
videoPages := [4][]uint8{
|
||||
cloneSlice(vs.GetVideoMemory(false, false)),
|
||||
cloneSlice(vs.GetVideoMemory(false, true)),
|
||||
cloneSlice(vs.GetVideoMemory(true, false)),
|
||||
cloneSlice(vs.GetVideoMemory(true, true)),
|
||||
}
|
||||
|
||||
knownModes := []int{
|
||||
ScreenModeGreen,
|
||||
ScreenModeNTSC,
|
||||
ScreenModePlain,
|
||||
}
|
||||
|
||||
return &TestScenario{
|
||||
vs.GetCurrentVideoMode(),
|
||||
VideoModeName(vs),
|
||||
knownModes,
|
||||
textPages,
|
||||
videoPages,
|
||||
cloneSlice(vs.GetSuperVideoMemory()),
|
||||
}
|
||||
}
|
||||
|
||||
func cloneSlice(src []uint8) []uint8 {
|
||||
dst := make([]uint8, len(src))
|
||||
copy(dst, src)
|
||||
return dst
|
||||
}
|
||||
|
||||
func loadTestScenario(filename string) (*TestScenario, error) {
|
||||
bytes, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var ts TestScenario
|
||||
err = json.Unmarshal(bytes, &ts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ts, nil
|
||||
}
|
||||
|
||||
func (ts *TestScenario) save(dir string) (string, error) {
|
||||
bytes, err := json.Marshal(ts)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
pattern := fmt.Sprintf("%v_*.json", strings.ToLower(ts.VideoModeName))
|
||||
file, err := ioutil.TempFile(dir, pattern)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
_, err = file.Write(bytes)
|
||||
return file.Name(), err
|
||||
}
|
||||
|
||||
// GetCurrentVideoMode returns the active video mode
|
||||
func (ts *TestScenario) GetCurrentVideoMode() uint16 {
|
||||
return ts.VideoMode
|
||||
}
|
||||
|
||||
func optionsToIndex(secondPage bool, ext bool) int {
|
||||
index := 0
|
||||
if secondPage {
|
||||
index += 2
|
||||
}
|
||||
if ext {
|
||||
index++
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
// GetTextMemory returns a slice to the text memory pages
|
||||
func (ts *TestScenario) GetTextMemory(secondPage bool, ext bool) []uint8 {
|
||||
return ts.TextPages[optionsToIndex(secondPage, ext)]
|
||||
}
|
||||
|
||||
// GetVideoMemory returns a slice to the video memory pages
|
||||
func (ts *TestScenario) GetVideoMemory(secondPage bool, ext bool) []uint8 {
|
||||
return ts.VideoPages[optionsToIndex(secondPage, ext)]
|
||||
}
|
||||
|
||||
// GetCharacterPixel returns the pixel as output by the character generator
|
||||
func (ts *TestScenario) GetCharacterPixel(char uint8, rowInChar int, colInChar int) bool {
|
||||
// We don't have a character generator. We will return a square or blank for spaces
|
||||
if char&0x3f == 0x20 {
|
||||
return false // Space char
|
||||
}
|
||||
|
||||
return !(rowInChar == 0 || rowInChar == 7 || colInChar == 0 || colInChar == 6)
|
||||
}
|
||||
|
||||
// GetSuperVideoMemory returns a slice to the SHR video memory
|
||||
func (ts *TestScenario) GetSuperVideoMemory() []uint8 {
|
||||
return ts.SVideoPage
|
||||
}
|
||||
|
||||
func buildImageName(name string, screenMode int, altSet bool) string {
|
||||
var screenName string
|
||||
switch screenMode {
|
||||
case ScreenModeGreen:
|
||||
screenName = "green"
|
||||
case ScreenModeNTSC:
|
||||
screenName = "ntsc"
|
||||
case ScreenModePlain:
|
||||
screenName = "plain"
|
||||
default:
|
||||
screenName = "unknown"
|
||||
}
|
||||
|
||||
if altSet {
|
||||
screenName += "_new"
|
||||
}
|
||||
|
||||
return strings.TrimSuffix(name, ".json") +
|
||||
screenName + ".png"
|
||||
}
|
||||
|
||||
func (ts *TestScenario) generateSnapshots(baseName string, altSet bool) error {
|
||||
for _, screen := range ts.ScreenModes {
|
||||
image := Snapshot(ts, screen)
|
||||
imageName := buildImageName(baseName, screen, altSet)
|
||||
f, err := os.Create(imageName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
png.Encode(f, image)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddScenario Generate a new video scenario
|
||||
func AddScenario(vs VideoSource, dir string) error {
|
||||
// Get memory contents
|
||||
ts := grabTestScenario(vs)
|
||||
|
||||
name, err := ts.save(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ts.generateSnapshots(name, false)
|
||||
}
|
1
screen/test_resources/dgr_187261985.json
Normal file
BIN
screen/test_resources/dgr_187261985green.png
Normal file
After Width: | Height: | Size: 6.8 KiB |
BIN
screen/test_resources/dgr_187261985ntsc.png
Normal file
After Width: | Height: | Size: 8.7 KiB |
BIN
screen/test_resources/dgr_187261985plain.png
Normal file
After Width: | Height: | Size: 3.9 KiB |
1
screen/test_resources/dhgr_106778142.json
Normal file
BIN
screen/test_resources/dhgr_106778142green.png
Normal file
After Width: | Height: | Size: 5.0 KiB |
BIN
screen/test_resources/dhgr_106778142ntsc.png
Normal file
After Width: | Height: | Size: 6.0 KiB |
BIN
screen/test_resources/dhgr_106778142plain.png
Normal file
After Width: | Height: | Size: 3.8 KiB |
1
screen/test_resources/dhgr_411937636.json
Normal file
BIN
screen/test_resources/dhgr_411937636green.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
screen/test_resources/dhgr_411937636ntsc.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
screen/test_resources/dhgr_411937636plain.png
Normal file
After Width: | Height: | Size: 19 KiB |
1
screen/test_resources/gr-mix40_143002050.json
Normal file
BIN
screen/test_resources/gr-mix40_143002050green.png
Normal file
After Width: | Height: | Size: 4.6 KiB |
BIN
screen/test_resources/gr-mix40_143002050ntsc.png
Normal file
After Width: | Height: | Size: 5.2 KiB |
BIN
screen/test_resources/gr-mix40_143002050plain.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
1
screen/test_resources/gr-mix40_255914052.json
Normal file
BIN
screen/test_resources/gr-mix40_255914052green.png
Normal file
After Width: | Height: | Size: 4.6 KiB |
BIN
screen/test_resources/gr-mix40_255914052ntsc.png
Normal file
After Width: | Height: | Size: 5.2 KiB |
BIN
screen/test_resources/gr-mix40_255914052plain.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
1
screen/test_resources/gr-mix40_299159992.json
Normal file
BIN
screen/test_resources/gr-mix40_299159992green.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
screen/test_resources/gr-mix40_299159992ntsc.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
BIN
screen/test_resources/gr-mix40_299159992plain.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
1
screen/test_resources/gr-mix80_161703993.json
Normal file
BIN
screen/test_resources/gr-mix80_161703993green.png
Normal file
After Width: | Height: | Size: 4.0 KiB |
BIN
screen/test_resources/gr-mix80_161703993ntsc.png
Normal file
After Width: | Height: | Size: 4.0 KiB |
BIN
screen/test_resources/gr-mix80_161703993plain.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
1
screen/test_resources/hgr-mix40_329392851.json
Normal file
BIN
screen/test_resources/hgr-mix40_329392851green.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
screen/test_resources/hgr-mix40_329392851ntsc.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
screen/test_resources/hgr-mix40_329392851plain.png
Normal file
After Width: | Height: | Size: 15 KiB |
1
screen/test_resources/hgr-mix80_217644054.json
Normal file
BIN
screen/test_resources/hgr-mix80_217644054green.png
Normal file
After Width: | Height: | Size: 5.4 KiB |
BIN
screen/test_resources/hgr-mix80_217644054ntsc.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
BIN
screen/test_resources/hgr-mix80_217644054plain.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
1
screen/test_resources/hgr_489333721.json
Normal file
BIN
screen/test_resources/hgr_489333721green.png
Normal file
After Width: | Height: | Size: 8.5 KiB |
BIN
screen/test_resources/hgr_489333721ntsc.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
screen/test_resources/hgr_489333721plain.png
Normal file
After Width: | Height: | Size: 12 KiB |
1
screen/test_resources/mono560_981947660.json
Normal file
BIN
screen/test_resources/mono560_981947660green.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
BIN
screen/test_resources/mono560_981947660ntsc.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
BIN
screen/test_resources/mono560_981947660plain.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
1
screen/test_resources/rgb160_076903675.json
Normal file
BIN
screen/test_resources/rgb160_076903675green.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
BIN
screen/test_resources/rgb160_076903675ntsc.png
Normal file
After Width: | Height: | Size: 7.6 KiB |
BIN
screen/test_resources/rgb160_076903675plain.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
1
screen/test_resources/rgbmix_772608229.json
Normal file
BIN
screen/test_resources/rgbmix_772608229green.png
Normal file
After Width: | Height: | Size: 5.4 KiB |
BIN
screen/test_resources/rgbmix_772608229ntsc.png
Normal file
After Width: | Height: | Size: 6.4 KiB |
BIN
screen/test_resources/rgbmix_772608229plain.png
Normal file
After Width: | Height: | Size: 4.0 KiB |
1
screen/test_resources/shr_039930416.json
Normal file
BIN
screen/test_resources/shr_039930416green.png
Normal file
After Width: | Height: | Size: 39 KiB |
BIN
screen/test_resources/shr_039930416ntsc.png
Normal file
After Width: | Height: | Size: 39 KiB |
BIN
screen/test_resources/shr_039930416plain.png
Normal file
After Width: | Height: | Size: 39 KiB |
1
screen/test_resources/text40col_792589108.json
Normal file
BIN
screen/test_resources/text40col_792589108green.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
screen/test_resources/text40col_792589108ntsc.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
screen/test_resources/text40col_792589108plain.png
Normal file
After Width: | Height: | Size: 925 B |
1
screen/test_resources/text40colrgb_455439485.json
Normal file
BIN
screen/test_resources/text40colrgb_455439485green.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
screen/test_resources/text40colrgb_455439485ntsc.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
screen/test_resources/text40colrgb_455439485plain.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
1
screen/test_resources/text80col_474745391.json
Normal file
BIN
screen/test_resources/text80col_474745391green.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
screen/test_resources/text80col_474745391ntsc.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
screen/test_resources/text80col_474745391plain.png
Normal file
After Width: | Height: | Size: 908 B |