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 {
|
switch keyEvent.Name {
|
||||||
case desktop.KeyControlLeft:
|
case desktop.KeyControlLeft:
|
||||||
k.controlLeft = press
|
k.controlLeft = press
|
||||||
|
@ -62,7 +72,10 @@ func (k *keyboard) putKey(keyEvent *fyne.KeyEvent) {
|
||||||
20 PRINT A, A - 128
|
20 PRINT A, A - 128
|
||||||
30 GOTO 10
|
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)
|
result := uint8(0)
|
||||||
switch keyEvent.Name {
|
switch keyEvent.Name {
|
||||||
case fyne.KeyEscape:
|
case fyne.KeyEscape:
|
||||||
|
@ -94,15 +107,11 @@ func (k *keyboard) putKey(keyEvent *fyne.KeyEvent) {
|
||||||
|
|
||||||
// Control of the emulator
|
// Control of the emulator
|
||||||
case fyne.KeyF1:
|
case fyne.KeyF1:
|
||||||
if ctrl {
|
/*if ctrl {
|
||||||
k.s.a.SendCommand(izapple2.CommandReset)
|
k.s.a.SendCommand(izapple2.CommandReset)
|
||||||
}
|
}*/
|
||||||
case fyne.KeyF5:
|
case fyne.KeyF5:
|
||||||
if ctrl {
|
k.s.a.SendCommand(izapple2.CommandShowSpeed)
|
||||||
k.s.a.SendCommand(izapple2.CommandShowSpeed)
|
|
||||||
} else {
|
|
||||||
k.s.a.SendCommand(izapple2.CommandToggleSpeed)
|
|
||||||
}
|
|
||||||
case fyne.KeyF6:
|
case fyne.KeyF6:
|
||||||
if k.s.screenMode != screen.ScreenModeGreen {
|
if k.s.screenMode != screen.ScreenModeGreen {
|
||||||
k.s.screenMode = screen.ScreenModeGreen
|
k.s.screenMode = screen.ScreenModeGreen
|
||||||
|
|
|
@ -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:
|
case VideoMono560:
|
||||||
name = "Mono560"
|
name = "Mono560"
|
||||||
case VideoRGBMix:
|
case VideoRGBMix:
|
||||||
name = "RGMMIX"
|
name = "RGBMIX"
|
||||||
case VideoRGB160:
|
case VideoRGB160:
|
||||||
name = "RGB160"
|
name = "RGB160"
|
||||||
case VideoSHR:
|
case VideoSHR:
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
After Width: | Height: | Size: 6.8 KiB |
After Width: | Height: | Size: 8.7 KiB |
After Width: | Height: | Size: 3.9 KiB |
After Width: | Height: | Size: 5.0 KiB |
After Width: | Height: | Size: 6.0 KiB |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 21 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 4.6 KiB |
After Width: | Height: | Size: 5.2 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 4.6 KiB |
After Width: | Height: | Size: 5.2 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 4.0 KiB |
After Width: | Height: | Size: 4.0 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 5.4 KiB |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 8.5 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 4.7 KiB |
After Width: | Height: | Size: 4.7 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 5.3 KiB |
After Width: | Height: | Size: 7.6 KiB |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 5.4 KiB |
After Width: | Height: | Size: 6.4 KiB |
After Width: | Height: | Size: 4.0 KiB |
After Width: | Height: | Size: 39 KiB |
After Width: | Height: | Size: 39 KiB |
After Width: | Height: | Size: 39 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 925 B |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 908 B |