github.com/grzegorz-zur/bm@v0.0.0-20240312214136-6fc133e3e2c0/editor_test.go (about)

     1  package main
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"fmt"
     7  	"github.com/gdamore/tcell"
     8  	"io"
     9  	"io/ioutil"
    10  	"log"
    11  	"os"
    12  	"path"
    13  	"strings"
    14  	"testing"
    15  	"time"
    16  	"unicode"
    17  )
    18  
    19  func TestEditor(t *testing.T) {
    20  	prefix := time.Now().Format("bm_2006-01-02_15-04-05_")
    21  	temp, err := ioutil.TempDir("", prefix)
    22  	if err != nil {
    23  		t.Fatalf("error creating temporary directory %s: %v", prefix, err)
    24  	}
    25  	names, err := list("test")
    26  	if err != nil {
    27  		t.Fatalf("error reading test directory: %v", err)
    28  	}
    29  	for _, name := range names {
    30  		test := test(name, temp)
    31  		t.Run(name, test)
    32  	}
    33  }
    34  
    35  func test(name, temp string) func(t *testing.T) {
    36  	return func(t *testing.T) {
    37  		work, files, err := setup(name, temp, t)
    38  		if err != nil {
    39  			t.Fatalf("error on setup: %s: %v", name, err)
    40  		}
    41  		cmds, err := commands(name)
    42  		if err != nil {
    43  			t.Fatalf("error parsing script %s: %v", name, err)
    44  		}
    45  
    46  		dir, err := os.Getwd()
    47  		if err != nil {
    48  			t.Fatalf("error getting current directory: %v", err)
    49  		}
    50  		defer os.Chdir(dir)
    51  		err = os.Chdir(work)
    52  		if err != nil {
    53  			t.Fatalf("error changing dir to %s: %v", work, err)
    54  		}
    55  
    56  		logpath := path.Join(temp, name+".log")
    57  		logfile, err := os.Create(logpath)
    58  		if err != nil {
    59  			t.Fatalf("error opening logfile %s: %v", logpath, err)
    60  			os.Exit(-1)
    61  		}
    62  		defer logfile.Close()
    63  		log.SetOutput(logfile)
    64  		defer log.SetOutput(os.Stderr)
    65  
    66  		screen := tcell.NewSimulationScreen("")
    67  		screenCreate := func() (tcell.Screen, error) {
    68  			return screen, nil
    69  		}
    70  		editor := New(screenCreate, files)
    71  		err = editor.Start()
    72  		if err != nil {
    73  			t.Fatalf("error starting editor: %v", err)
    74  		}
    75  		sendResize(screen, 40, 25)
    76  		sendResize(screen, 80, 50)
    77  
    78  		err = interpret(screen, editor, cmds)
    79  		if err != nil {
    80  			t.Fatalf("error interpreting script: %s: %v", name, err)
    81  		}
    82  
    83  		editor.Wait()
    84  
    85  		logfile.Close()
    86  		log.SetOutput(os.Stderr)
    87  
    88  		err = os.Chdir(dir)
    89  		if err != nil {
    90  			t.Fatalf("error changing dir to %s: %v", dir, err)
    91  		}
    92  		err = verify(name, work, t)
    93  		if err != nil {
    94  			t.Fatalf("wrong results: %s: %v", name, err)
    95  		}
    96  	}
    97  }
    98  
    99  func setup(name, temp string, t *testing.T) (work string, files []string, err error) {
   100  	in := path.Join("test", name, "in")
   101  	files, err = list(in)
   102  	if err != nil && !os.IsNotExist(err) {
   103  		return "", nil, fmt.Errorf("error listing path %s: %w", in, err)
   104  	}
   105  	work = path.Join(temp, name)
   106  	err = os.MkdirAll(work, os.ModePerm)
   107  	if err != nil {
   108  		return "", nil, fmt.Errorf("error creating directory %s: %w", work, err)
   109  	}
   110  	for _, file := range files {
   111  		src := path.Join(in, file)
   112  		dst := path.Join(work, file)
   113  		err := copy(src, dst)
   114  		if err != nil {
   115  			return "", nil, fmt.Errorf("error copying from %s to %s: %w", src, dst, err)
   116  		}
   117  	}
   118  	return
   119  }
   120  
   121  func commands(name string) (cmds []string, err error) {
   122  	path := path.Join("test", name, "script")
   123  	file, err := os.Open(path)
   124  	if err != nil {
   125  		return nil, fmt.Errorf("error opening file %s: %w", path, err)
   126  	}
   127  	defer file.Close()
   128  	scanner := bufio.NewScanner(file)
   129  	scanner.Split(bufio.ScanWords)
   130  	for scanner.Scan() {
   131  		err = scanner.Err()
   132  		if err != nil {
   133  			return nil, fmt.Errorf("error scanning file %s: %w", path, err)
   134  		}
   135  		cmd := scanner.Text()
   136  		cmds = append(cmds, cmd)
   137  	}
   138  	return
   139  }
   140  
   141  func verify(name, work string, t *testing.T) (err error) {
   142  	out := path.Join("test", name, "out")
   143  	expected, err := list(out)
   144  	if err != nil {
   145  		return fmt.Errorf("error listing path %s: %w", out, err)
   146  	}
   147  	actual, err := list(work)
   148  	if err != nil {
   149  		return fmt.Errorf("error listing path %s: %w", work, err)
   150  	}
   151  	if len(expected) != len(actual) {
   152  		t.Logf("expected files %v", expected)
   153  		t.Logf("actual files %v", actual)
   154  		t.FailNow()
   155  	}
   156  	for i := range expected {
   157  		if actual[i] != expected[i] {
   158  			t.Logf("expected files %v", expected)
   159  			t.Logf("actual files %v", actual)
   160  			t.FailNow()
   161  		}
   162  	}
   163  	for _, exp := range expected {
   164  		actualPath := path.Join(work, exp)
   165  		actualContent, err := ioutil.ReadFile(actualPath)
   166  		if err != nil {
   167  			return fmt.Errorf("error reading file %s: %w", actualPath, err)
   168  		}
   169  		expectedPath := path.Join("test", name, "out", exp)
   170  		expectedContent, err := ioutil.ReadFile(expectedPath)
   171  		if err != nil {
   172  			return fmt.Errorf("error reading file %s: %w", expectedPath, err)
   173  		}
   174  		if bytes.Compare(actualContent, expectedContent) != 0 {
   175  			t.Log("comparing file " + exp)
   176  			t.Log("expected content")
   177  			t.Log(format(expectedContent))
   178  			t.Log("actual content")
   179  			t.Log(format(actualContent))
   180  			t.FailNow()
   181  		}
   182  	}
   183  	return
   184  }
   185  
   186  func format(content []byte) (text string) {
   187  	text = string(content)
   188  	text = strings.Replace(text, " ", "␣", -1)
   189  	text = strings.Replace(text, "\t", "␉", -1)
   190  	text = strings.Replace(text, "\r", "␍", -1)
   191  	text = strings.Replace(text, "\n", "␊", -1)
   192  	return
   193  }
   194  
   195  func interpret(screen tcell.Screen, editor *Editor, commands []string) (err error) {
   196  	for _, cmd := range commands {
   197  		runes := []rune(cmd)
   198  		switch {
   199  		case len(cmd) == 1:
   200  			rune := runes[0]
   201  			sendRune(screen, rune)
   202  		case len(cmd) == 2 && runes[0] == '^':
   203  			letter := unicode.ToUpper(runes[1])
   204  			offset := int(letter - 'A')
   205  			key := tcell.KeyCtrlA + tcell.Key(offset)
   206  			sendKey(screen, key)
   207  		case cmd == "left":
   208  			sendKey(screen, tcell.KeyLeft)
   209  		case cmd == "right":
   210  			sendKey(screen, tcell.KeyRight)
   211  		case cmd == "up":
   212  			sendKey(screen, tcell.KeyUp)
   213  		case cmd == "down":
   214  			sendKey(screen, tcell.KeyDown)
   215  		case cmd == "home":
   216  			sendKey(screen, tcell.KeyHome)
   217  		case cmd == "end":
   218  			sendKey(screen, tcell.KeyEnd)
   219  		case cmd == "pageup":
   220  			sendKey(screen, tcell.KeyPgUp)
   221  		case cmd == "pagedown":
   222  			sendKey(screen, tcell.KeyPgDn)
   223  		case cmd == "space":
   224  			sendRune(screen, ' ')
   225  		case cmd == "tab":
   226  			sendKey(screen, tcell.KeyTab)
   227  		case cmd == "enter":
   228  			sendKey(screen, tcell.KeyEnter)
   229  		case cmd == "backspace":
   230  			sendKey(screen, tcell.KeyBackspace2)
   231  		case cmd == "delete":
   232  			sendKey(screen, tcell.KeyDelete)
   233  		case cmd == "ctrlspace":
   234  			sendKey(screen, tcell.KeyCtrlSpace)
   235  		case cmd == "TOUCH":
   236  			t := time.Now().Local()
   237  			err = os.Chtimes(editor.File.Path, t, t)
   238  			if err != nil {
   239  				return fmt.Errorf("error changing time of %s: %w", editor.File.Path, err)
   240  			}
   241  		case cmd == "CHECK":
   242  			time.Sleep(2 * TickInterval)
   243  		default:
   244  			return fmt.Errorf("unknown command: %s", cmd)
   245  		}
   246  	}
   247  	return
   248  }
   249  
   250  func sendResize(screen tcell.Screen, width, height int) {
   251  	event := tcell.NewEventResize(width, height)
   252  	screen.PostEventWait(event)
   253  }
   254  
   255  func sendKey(screen tcell.Screen, key tcell.Key) {
   256  	event := tcell.NewEventKey(key, 0, 0)
   257  	screen.PostEventWait(event)
   258  }
   259  
   260  func sendRune(screen tcell.Screen, rune rune) {
   261  	event := tcell.NewEventKey(tcell.KeyRune, rune, 0)
   262  	screen.PostEventWait(event)
   263  }
   264  
   265  func list(path string) (names []string, err error) {
   266  	files, err := ioutil.ReadDir(path)
   267  	if err != nil && !os.IsNotExist(err) {
   268  		return nil, fmt.Errorf("error reading directory %s: %w", path, err)
   269  	}
   270  	for _, file := range files {
   271  		name := file.Name()
   272  		names = append(names, name)
   273  	}
   274  	return
   275  }
   276  
   277  func copy(src, dst string) (err error) {
   278  	in, err := os.Open(src)
   279  	if err != nil {
   280  		return fmt.Errorf("error opening file %s: %w", src, err)
   281  	}
   282  	defer in.Close()
   283  
   284  	out, err := os.Create(dst)
   285  	if err != nil {
   286  		return fmt.Errorf("error creating file %s: %w", src, err)
   287  	}
   288  	defer out.Close()
   289  
   290  	_, err = io.Copy(out, in)
   291  	if err != nil {
   292  		return fmt.Errorf("error copying from %s to %s: %w", src, dst, err)
   293  	}
   294  	return out.Close()
   295  }