Added Ubuntu workaround
[far2l.git] / testing / far2l-smoke.go
blob138bc13621290fb6008f4f7bbc8b22ab7fea58dc
1 package main
3 import (
4 "log"
5 "os"
6 "net"
7 "fmt"
8 "time"
9 "strings"
10 "strconv"
11 "os/exec"
12 "io"
13 "io/ioutil"
14 "io/fs"
15 "math/rand"
16 "hash"
17 "crypto/sha256"
18 "encoding/binary"
19 "path/filepath"
20 "github.com/ActiveState/termtest"
21 "github.com/ActiveState/termtest/expect"
22 "github.com/dop251/goja"
25 type far2l_Status struct {
26 Title string
27 Width uint32
28 Height uint32
29 CurX uint32
30 CurY uint32
31 CurH uint8
32 CurV bool
35 type far2l_FoundString struct {
36 I uint32
37 X uint32
38 Y uint32
41 type far2l_CellRaw struct {
42 Text string
43 Attributes uint64
46 type far2l_Cell struct {
47 Text string
48 BackTC uint32
49 ForeTC uint32
50 Back uint8
51 Fore uint8
52 IsBackTC bool
53 IsForeTC bool
54 ForeBlue bool
55 ForeGreen bool
56 ForeRed bool
57 ForeIntense bool
58 BackBlue bool
59 BackGreen bool
60 BackRed bool
61 BackIntense bool
62 ReverseVideo bool
63 Underscore bool
64 Strikeout bool
67 var g_far2l_sock string
68 var g_far2l_bin string
69 var g_socket *net.UnixConn
70 var g_addr *net.UnixAddr
71 var g_buf [4096]byte
72 var g_app *termtest.ConsoleProcess
73 var g_vm *goja.Runtime
74 var g_status far2l_Status
75 var g_far2l_running bool = false
76 var g_lctrl bool
77 var g_rctrl bool
78 var g_lalt bool
79 var g_ralt bool
80 var g_shift bool
81 var g_recv_timeout uint32 = 30
82 var g_test_workdir string
83 var g_calm bool = false
84 var g_last_error string
86 func stringFromBytes(buf []byte) string {
87 last := 0
88 for ; last < len(buf) && buf[last] != 0; last++ {
90 return string(buf[0:last])
93 func aux_BePanic() {
94 g_calm = false
97 func aux_BeCalm() {
98 g_calm = true
101 func aux_Inspect() string {
102 out:= g_last_error
103 g_last_error = ""
104 return out
107 func setErrorString(err string) {
108 g_last_error = err
109 log.Print("\x1b[1;31mERROR: " + err + "\x1b[39;22m")
110 if !g_calm {
111 aux_Panic(err)
115 func assertNoError(err error) bool {
116 if err != nil {
117 setErrorString(err.Error())
118 return false
120 return true
123 func far2l_ReadSocket(expected_n int, extra_timeout uint32) {
124 err := g_socket.SetReadDeadline(time.Now().Add(time.Duration(g_recv_timeout + extra_timeout) * time.Second))
125 if err != nil {
126 aux_Panic(err.Error())
128 n, addr, err := g_socket.ReadFromUnix(g_buf[:])
129 if err != nil || n != expected_n {
130 if g_addr == nil {
131 if net_err, ok := err.(net.Error); ok && net_err.Timeout() {
132 aux_Panic("First communication timed out, make sure application built with testing support or increase timeout by -t argument")
135 aux_Panic(err.Error())
137 if g_addr == nil || *g_addr != *addr {
138 g_addr = addr
139 log.Printf("Peer: %v", g_addr)
143 func far2l_Close() {
144 if g_far2l_running {
145 g_far2l_running = false
146 g_app.Close()
150 func far2l_Start(args []string) far2l_Status {
151 if g_far2l_running {
152 aux_Panic("far2l already running")
154 opts := termtest.Options {
155 CmdName: g_far2l_bin,
156 Args: append([]string{g_far2l_bin, "--test=" + g_far2l_sock}, args...),
157 Environment : []string {
158 "FAR2L_STD=" + filepath.Join(g_test_workdir, "far2l.log"),
159 "FAR2L_TESTCTL=" + g_far2l_sock},
160 ExtraOpts: []expect.ConsoleOpt{expect.WithTermRows(80), expect.WithTermCols(120)},
162 var err error
163 g_app, err = termtest.New(opts)
164 if err != nil {
165 aux_Panic(err.Error())
167 g_far2l_running = true
168 return far2l_RecvStatus()
171 func far2l_ReqRecvStatus() far2l_Status {
172 binary.LittleEndian.PutUint32(g_buf[0:], 1)
173 n, err := g_socket.WriteTo(g_buf[0:4], g_addr)
174 if err != nil || n != 4 {
175 aux_Panic(err.Error())
177 return far2l_RecvStatus()
180 func far2l_RecvStatus() far2l_Status {
181 far2l_ReadSocket(2068, 0)
182 g_status.Title = stringFromBytes(g_buf[20:])
183 g_status.CurH = g_buf[2]
184 g_status.CurV = g_buf[3] != 0
185 g_status.CurX = binary.LittleEndian.Uint32(g_buf[4:])
186 g_status.CurY = binary.LittleEndian.Uint32(g_buf[8:])
187 g_status.Width = binary.LittleEndian.Uint32(g_buf[12:])
188 g_status.Height = binary.LittleEndian.Uint32(g_buf[16:])
189 return g_status
192 func far2l_ReqRecvExpectString(str string, x uint32, y uint32, w uint32, h uint32, tmout uint32) far2l_FoundString {
193 return far2l_ReqRecvExpectXStrings([]string{str}, x, y, w, h, tmout, true)
196 func far2l_ReqRecvExpectStrings(str_vec []string, x uint32, y uint32, w uint32, h uint32, tmout uint32) far2l_FoundString {
197 return far2l_ReqRecvExpectXStrings(str_vec, x, y, w, h, tmout, true)
200 func far2l_ReqRecvExpectNoString(str string, x uint32, y uint32, w uint32, h uint32, tmout uint32) far2l_FoundString {
201 return far2l_ReqRecvExpectXStrings([]string{str}, x, y, w, h, tmout, false)
204 func far2l_ReqRecvExpectNoStrings(str_vec []string, x uint32, y uint32, w uint32, h uint32, tmout uint32) far2l_FoundString {
205 return far2l_ReqRecvExpectXStrings(str_vec, x, y, w, h, tmout, false)
208 func far2l_ReqRecvExpectXStrings(str_vec []string, x uint32, y uint32, w uint32, h uint32, tmout uint32, need_presence bool) far2l_FoundString {
209 if w == 0xffffffff { w = g_status.Width; }
210 if h == 0xffffffff { h = g_status.Height; }
211 if (need_presence) {
212 binary.LittleEndian.PutUint32(g_buf[0:], 3) //TEST_CMD_WAIT_STRING
213 } else {
214 binary.LittleEndian.PutUint32(g_buf[0:], 4) //TEST_CMD_WAIT_NO_STRING
216 binary.LittleEndian.PutUint32(g_buf[4:], tmout)
217 binary.LittleEndian.PutUint32(g_buf[8:], x) //left
218 binary.LittleEndian.PutUint32(g_buf[12:], y) //top
219 binary.LittleEndian.PutUint32(g_buf[16:], w) //width
220 binary.LittleEndian.PutUint32(g_buf[20:], h) //height
221 p := 0
222 for i := 0; i < len(str_vec); i++ {
223 str_bytes:= []byte(str_vec[i])
224 for j := 0; j < len(str_bytes); j++ {
225 g_buf[24 + p] = str_bytes[j]
228 g_buf[24 + p] = 0
231 if p >= 2048 {
232 aux_Panic("Too long strings")
234 for ; p < 2048; p++ {
235 g_buf[24 + p] = 0
238 n, err := g_socket.WriteTo(g_buf[0:24 + 2048], g_addr)
239 if err != nil || n != 24 + 2048 {
240 aux_Panic(err.Error())
242 far2l_ReadSocket(12, tmout / 1000)
243 out := far2l_FoundString {
244 I: binary.LittleEndian.Uint32(g_buf[0:]),
245 X: binary.LittleEndian.Uint32(g_buf[4:]),
246 Y: binary.LittleEndian.Uint32(g_buf[8:]),
248 var status string
249 if out.I < uint32(len(str_vec)) {
250 status = fmt.Sprintf("String at [%d : %d] - %v", out.X, out.Y, str_vec[out.I])
251 } else {
252 status = fmt.Sprintf("Nothing at [%d +%d : %d +%d] of %v", x, w, y, h, str_vec)
254 if (need_presence) {
255 if out.I < uint32(len(str_vec)) {
256 fmt.Println(status)
257 } else {
258 setErrorString(status)
260 } else if out.I < uint32(len(str_vec)) {
261 setErrorString(status)
262 } else {
263 fmt.Println(status)
265 return out
268 func far2l_ReqRecvReadCellRaw(x uint32, y uint32) far2l_CellRaw {
269 binary.LittleEndian.PutUint32(g_buf[0:], 2) // TEST_CMD_READ_CELL
270 binary.LittleEndian.PutUint32(g_buf[4:], x) // left
271 binary.LittleEndian.PutUint32(g_buf[8:], y) // top
272 n, err := g_socket.WriteTo(g_buf[0:12], g_addr)
273 if err != nil || n != 12 {
274 aux_Panic(err.Error())
276 far2l_ReadSocket(2056, 0)
277 return far2l_CellRaw {
278 Text: stringFromBytes(g_buf[8:]),
279 Attributes: binary.LittleEndian.Uint64(g_buf[0:]),
283 func far2l_ReqRecvReadCell(x uint32, y uint32) far2l_Cell {
284 raw_cell := far2l_ReqRecvReadCellRaw(x, y)
285 return far2l_Cell {
286 Text: raw_cell.Text,
287 BackTC: uint32((raw_cell.Attributes >> 40) & 0xFFFFFF),
288 ForeTC: uint32((raw_cell.Attributes >> 16) & 0xFFFFFF),
289 Back: uint8(((raw_cell.Attributes >> 4) & 0xF)),
290 Fore: uint8((raw_cell.Attributes & 0xF)),
291 IsBackTC: (raw_cell.Attributes & 0x0200) != 0,
292 IsForeTC: (raw_cell.Attributes & 0x0100) != 0,
293 ForeBlue: (raw_cell.Attributes & 0x0001) != 0,
294 ForeGreen: (raw_cell.Attributes & 0x0002) != 0,
295 ForeRed: (raw_cell.Attributes & 0x0004) != 0,
296 ForeIntense: (raw_cell.Attributes & 0x0008) != 0,
297 BackBlue: (raw_cell.Attributes & 0x0010) != 0,
298 BackGreen: (raw_cell.Attributes & 0x0020) != 0,
299 BackRed: (raw_cell.Attributes & 0x0040) != 0,
300 BackIntense: (raw_cell.Attributes & 0x0080) != 0,
301 ReverseVideo: (raw_cell.Attributes & 0x4000) != 0,
302 Underscore: (raw_cell.Attributes & 0x8000) != 0,
303 Strikeout: (raw_cell.Attributes & 0x2000) != 0,
307 func far2l_CheckBoundedLine(expected string, left uint32, top uint32, width uint32, trim_chars string) string {
308 line:= far2l_BoundedLine(left, top, width, trim_chars)
309 if line != expected {
310 setErrorString(fmt.Sprintf("Line at [%d +%d : %d] not expected: '%v'", left, width, top, line))
312 return line
315 func far2l_BoundedLine(left uint32, top uint32, width uint32, trim_chars string) string {
316 lines:= far2l_BoundedLines(left, top, width, 1, trim_chars)
317 return lines[0]
320 func far2l_BoundedLines(left uint32, top uint32, width uint32, height uint32, trim_chars string) []string {
321 lines:= []string{}
322 for y := top; y < top + height; y++ {
323 line:= ""
324 for x := left; x < left + width; x++ {
325 cell := far2l_ReqRecvReadCellRaw(x, y)
326 line += cell.Text
328 if trim_chars != "" {
329 lines = append(lines, strings.Trim(line, trim_chars))
330 } else {
331 lines = append(lines, line)
334 return lines
337 func far2l_SurroundedLines(x uint32, y uint32, boundary_chars string, trim_chars string) []string {
338 var left, top, width, height uint32
339 far2l_ReqRecvStatus()
341 for left = x; left > 0 && !far2l_CellCharMatches(left - 1, y, boundary_chars); left-- {
344 for width = 1; left + width < g_status.Width && !far2l_CellCharMatches(left + width, y, boundary_chars); width++ {
347 // top & bottom edges has some quirks due to they may contains caption, hints, time etc..
348 for top = y; top > 0 &&
349 !far2l_CellCharMatches(left, top - 1, boundary_chars) &&
350 !far2l_CellCharMatches(left + width / 2, top - 1, boundary_chars) &&
351 !far2l_CellCharMatches(left + width - 1, top - 1, boundary_chars); top-- {
354 for height = 1; top + height < g_status.Height &&
355 !far2l_CellCharMatches(left, top + height, boundary_chars) &&
356 !far2l_CellCharMatches(left + width / 2, top + height, boundary_chars) &&
357 !far2l_CellCharMatches(left + width - 1, top + height, boundary_chars); height++ {
360 return far2l_BoundedLines(left, top, width, height, trim_chars)
363 func far2l_CellCharMatches(x uint32, y uint32, chars string) bool {
364 cell := far2l_ReqRecvReadCellRaw(x, y)
365 return cell.Text != "" && strings.Contains(chars, cell.Text)
368 func far2l_CheckCellChar(x uint32, y uint32, chars string) string {
369 cell := far2l_ReqRecvReadCellRaw(x, y)
370 if cell.Text == "" || !strings.Contains(chars, cell.Text) {
371 setErrorString(fmt.Sprintf("Cell at %d:%d = '%s' doesnt represent any of: '%s'", x, y, cell.Text, chars))
373 return cell.Text
376 func far2l_ReqBye() {
377 binary.LittleEndian.PutUint32(g_buf[0:], 0)
378 n, err := g_socket.WriteTo(g_buf[0:4], g_addr)
379 if err != nil || n != 4 {
380 aux_Panic(err.Error())
384 func far2l_ExpectExit(code int, timeout_ms int) string {
385 far2l_ReqBye()
386 _, err:= g_app.ExpectExitCode(code, time.Duration(timeout_ms) * 1000000)
387 if err != nil {
388 setErrorString(fmt.Sprintf("ExpectExit: %v", err))
389 return "ERROR:" + err.Error()
391 far2l_Close()
392 return ""
395 func aux_Log(message string) {
396 log.Print(message)
399 func aux_Panic(message string) {
400 panic("\x1b[1;31m" + message + "\x1b[39;22m")
403 func tty_Write(s string) {
404 g_app.Send(s)
407 func tty_CtrlC() {
408 g_app.SendCtrlC()
411 func far2l_ToggleShift(pressed bool) {
412 g_shift = pressed
413 far2l_SendKeyEvent(0, 0x10, pressed)
416 func far2l_ToggleLCtrl(pressed bool) {
417 g_lctrl = pressed
418 far2l_SendKeyEvent(0, 0x11, pressed)
421 func far2l_ToggleRCtrl(pressed bool) {
422 g_rctrl = pressed
423 far2l_SendKeyEvent(0, 0x11, pressed)
426 func far2l_ToggleLAlt(pressed bool) {
427 g_lalt = pressed
428 far2l_SendKeyEvent(0, 0x12, pressed)
431 func far2l_ToggleRAlt(pressed bool) {
432 g_ralt = pressed
433 far2l_SendKeyEvent(0, 0x12, pressed)
436 func far2l_TypeFKey(n uint32) { far2l_TypeVK(0x6F + n) }
437 func far2l_TypeDigit(n uint32) { far2l_TypeVK(0x60 + n) }
439 func far2l_TypeAdd() { far2l_TypeVK(0x6B) }
440 func far2l_TypeSub() { far2l_TypeVK(0x6D) }
441 func far2l_TypeMul() { far2l_TypeVK(0x6A) }
442 func far2l_TypeDiv() { far2l_TypeVK(0x6F) }
443 func far2l_TypeSeparator(){ far2l_TypeVK(0x6C) }
444 func far2l_TypeDecimal() { far2l_TypeVK(0x6E) }
446 func far2l_TypeBack() { far2l_TypeVK(0x08) }
447 func far2l_TypeEnter() { far2l_TypeVK(0x0D) }
448 func far2l_TypeEscape() { far2l_TypeVK(0x1B) }
449 func far2l_TypePageUp() { far2l_TypeVK(0x21) }
450 func far2l_TypePageDown() { far2l_TypeVK(0x22) }
451 func far2l_TypeEnd() { far2l_TypeVK(0x23) }
452 func far2l_TypeHome() { far2l_TypeVK(0x24) }
453 func far2l_TypeLeft() { far2l_TypeVK(0x25) }
454 func far2l_TypeUp() { far2l_TypeVK(0x26) }
455 func far2l_TypeRight() { far2l_TypeVK(0x27) }
456 func far2l_TypeDown() { far2l_TypeVK(0x28) }
457 func far2l_TypeIns() { far2l_TypeVK(0x2D) }
458 func far2l_TypeDel() { far2l_TypeVK(0x2E) }
461 func far2l_TypeVK(key_code uint32) {
462 far2l_SendKeyEvent(0, key_code, true)
463 far2l_SendKeyEvent(0, key_code, false)
467 func far2l_TypeText(text string) {
468 for _, r := range text {
469 far2l_SendKeyEvent(uint32(r), 0, true)
470 far2l_SendKeyEvent(uint32(r), 0, false)
474 func far2l_SendKeyEvent(utf32_code uint32, key_code uint32, pressed bool) {
475 if key_code == 0 && utf32_code != 0 {
476 if utf32_code >= 'a' && utf32_code <= 'z' {
477 key_code = 'A' + (utf32_code - 'a')
478 } else if (utf32_code <= 0x7f) {
479 key_code = utf32_code
482 var controls uint32 = 0
483 if g_lctrl { controls |= 0x0008 } // LEFT_CTRL_PRESSED
484 if g_rctrl { controls |= 0x0004 } // RIGHT_CTRL_PRESSED
485 if g_lalt { controls |= 0x0002 } // LEFT_ALT_PRESSED
486 if g_ralt { controls |= 0x0001 } // RIGHT_ALT_PRESSED
487 if g_shift { controls |= 0x0010 } // SHFIT_PRESSED
488 binary.LittleEndian.PutUint32(g_buf[0:], 5) // TEST_CMD_SEND_KEY
489 binary.LittleEndian.PutUint32(g_buf[4:], controls)
490 binary.LittleEndian.PutUint32(g_buf[8:], utf32_code)
491 binary.LittleEndian.PutUint32(g_buf[12:], key_code)
492 binary.LittleEndian.PutUint32(g_buf[16:], 0)
493 binary.LittleEndian.PutUint32(g_buf[20:], 0)
494 if pressed { g_buf[20] = 1 }
495 n, err := g_socket.WriteTo(g_buf[0:24], g_addr)
496 if err != nil || n != 24 {
497 aux_Panic(err.Error())
501 func aux_RunCmd(args []string) string {
502 prog, err := exec.LookPath(args[0])
503 if err != nil {
504 return err.Error()
506 cmd := exec.Command(prog, args...)
507 err = cmd.Run()
508 if (err != nil) {
509 setErrorString("RunCmd: " + err.Error())
511 return ""
514 func aux_Sleep(msec uint32) {
515 time.Sleep(time.Duration(msec) * time.Millisecond)
518 func aux_WorkDir() string {
519 return g_test_workdir
522 func aux_Chmod(name string, mode os.FileMode) bool {
523 return assertNoError(os.Chmod(name, mode))
526 func aux_Chown(name string, uid, gid int) bool {
527 return assertNoError(os.Chown(name, uid, gid))
530 func aux_Chtimes(name string, atime time.Time, mtime time.Time) bool {
531 return assertNoError(os.Chtimes(name, atime, mtime))
534 func aux_Mkdir(name string, perm os.FileMode) bool {
535 return assertNoError(os.Mkdir(name, perm))
538 func aux_MkdirTemp(dir, pattern string) string {
539 out, err:= os.MkdirTemp(dir, pattern)
540 if !assertNoError(err) {
541 return ""
543 return out
546 func aux_Remove(name string) bool {
547 return assertNoError(os.Remove(name))
550 func aux_RemoveAll(name string) bool {
551 return assertNoError(os.RemoveAll(name))
554 func aux_Rename(oldpath, newpath string) bool {
555 return assertNoError(os.Rename(oldpath, newpath))
558 func aux_ReadFile(name string) []byte {
559 out, err:= os.ReadFile(name)
560 if !assertNoError(err) {
561 return []byte{}
563 return out
566 func aux_WriteFile(name string, data []byte, perm os.FileMode) bool {
567 return assertNoError(os.WriteFile(name, data, perm))
570 func aux_Truncate(name string, size int64) bool {
571 return assertNoError(os.Truncate(name, size))
574 func aux_ReadDir(name string) []os.DirEntry {
575 out, err := os.ReadDir(name)
576 if !assertNoError(err) {
577 return []os.DirEntry{}
579 return out
582 func aux_Symlink(oldname, newname string) bool {
583 return assertNoError(os.Symlink(oldname, newname))
586 func aux_Readlink(name string) string {
587 out, err:= os.Readlink(name)
588 if !assertNoError(err) {
589 return ""
591 return out
594 func aux_MkdirAll(path string, perm os.FileMode) bool {
595 return assertNoError(os.MkdirAll(path, perm))
598 func aux_MkdirsAll(pathes []string, perm os.FileMode) bool {
599 out:= true
600 for _, path := range pathes {
601 if !aux_MkdirAll(path, perm) {
602 out = false
605 return out
608 type LimitedRandomReader struct {
609 remain uint64
612 func (r *LimitedRandomReader) Read(p []byte) (n int, err error) {
613 if r.remain == 0 {
614 return 0, io.EOF
616 piece := len(p)
617 if piece == 0 {
618 return 0, nil
620 if uint64(piece) > r.remain {
621 piece = int(r.remain)
623 piece, err = rand.Read(p[0 : piece])
624 if err == nil {
625 if uint64(piece) < r.remain {
626 r.remain -= uint64(piece)
627 } else {
628 r.remain = 0
631 return piece, err
634 func aux_Mkfile(path string, mode os.FileMode, min_size uint64, max_size uint64) bool {
635 f, err := os.OpenFile(path, os.O_WRONLY | os.O_CREATE | os.O_TRUNC, mode)
636 if err != nil {
637 setErrorString(fmt.Sprintf("Error %v creating %v", err, path))
638 return false
640 defer f.Close()
642 lrr := LimitedRandomReader {
643 remain : min_size,
645 if max_size > min_size {
646 lrr.remain+= rand.Uint64() % (max_size - min_size)
649 _, err = io.Copy(f, &lrr)
650 if err != nil {
651 setErrorString(fmt.Sprintf("Error %v writing %v", err, path))
652 return false
655 return true
658 func aux_Mkfiles(pathes []string, mode os.FileMode, min_size uint64, max_size uint64) bool {
659 out:= true
660 for _, path := range pathes {
661 if !aux_Mkfile(path, mode, min_size, max_size) {
662 out = false
665 return out
668 var g_hash_data, g_hash_name, g_hash_mode, g_hash_link, g_hash_times bool
669 var g_hash_hide_path string
670 var g_hash hash.Hash
672 func EnhashString(s string) {
673 g_hash.Write([]byte(s))
676 func EnhashFileData(path string) {
677 f, err := os.Open(path)
678 if err != nil {
679 EnhashString("ERR:" + err.Error())
680 log.Printf("EnhashFileData:" + err.Error())
681 return
683 defer f.Close()
684 if _, err := io.Copy(g_hash, f); err != nil {
685 EnhashString("ERR:" + err.Error())
686 log.Printf("EnhashFileData:" + err.Error())
690 func EnhashFSObject(path string) bool {
691 var fi fs.FileInfo
692 var err error
693 if g_hash_link {
694 fi, err = os.Lstat(path)
695 } else {
696 fi, err = os.Stat(path)
698 if g_hash_name && path != g_hash_hide_path {
699 EnhashString(path)
701 if err != nil {
702 log.Printf("Stat error %s while enhashing %s", err.Error(), path)
703 return false
705 if g_hash_mode {
706 EnhashString(fi.Mode().String())
708 if g_hash_times {
709 // one second precision to avoid rounding errors
710 EnhashString( fi.ModTime().Format(time.Stamp))
712 if g_hash_data {
713 if (fi.Mode() & fs.ModeSymlink) != 0 {
714 if dst, err := os.Readlink(path); err == nil {
715 EnhashString(dst)
716 } else {
717 EnhashString(err.Error())
719 return false
721 if fi.Mode().IsRegular() {
722 EnhashFileData(path)
723 return false
726 return fi.IsDir()
729 func walkHash(path string, de fs.DirEntry, err error) error {
730 return nil
733 func aux_HashPath(path string, hash_data bool, hash_name bool, hash_link bool, hash_mode bool, hash_times bool) string {
734 return aux_HashPathes([]string{path}, hash_data, hash_name, hash_link, hash_mode, hash_times)
737 func aux_HashPathes(pathes []string, hash_data bool, hash_name bool, hash_link bool, hash_mode bool, hash_times bool) string {
738 g_hash_data = hash_data
739 g_hash_name = hash_name
740 g_hash_link = hash_link
741 g_hash_mode = hash_mode
742 g_hash_times = hash_times
743 g_hash = sha256.New()
744 for _, path := range pathes {
745 g_hash_hide_path = path
746 if EnhashFSObject(path) {
747 filepath.WalkDir(path, walkHash)
750 return fmt.Sprintf("%x", g_hash.Sum(nil))
753 func aux_Exists(path string) bool {
754 _, err := os.Lstat(path)
755 return err == nil
758 func aux_CountExisting(pathes []string) int {
759 out:= 0
760 for _, path := range pathes {
761 if aux_Exists(path) {
762 out++
765 return out
769 func initVM() {
770 /* initialize */
771 fmt.Println("Initializing JS VM...")
772 g_vm = goja.New()
774 /* goja does not expose a standard "global" by default */
775 _, err:= g_vm.RunString("var global = (function(){ return this; }).call(null);")
776 if err != nil { aux_Panic(err.Error()) }
778 setVMFunction("BePanic", aux_BePanic)
779 setVMFunction("BeCalm", aux_BeCalm)
780 setVMFunction("Inspect", aux_Inspect)
782 setVMFunction("StartApp", far2l_Start)
783 setVMFunction("AppStatus", far2l_ReqRecvStatus)
784 setVMFunction("ReadCellRaw", far2l_ReqRecvReadCellRaw)
785 setVMFunction("ReadCell", far2l_ReqRecvReadCell)
786 setVMFunction("CellCharMatches", far2l_CellCharMatches)
787 setVMFunction("CheckCellChar", far2l_CheckCellChar)
789 setVMFunction("BoundedLines", far2l_BoundedLines)
790 setVMFunction("BoundedLine", far2l_BoundedLine)
791 setVMFunction("CheckBoundedLine", far2l_CheckBoundedLine)
792 setVMFunction("SurroundedLines", far2l_SurroundedLines)
794 setVMFunction("ExpectStrings", far2l_ReqRecvExpectStrings)
795 setVMFunction("ExpectString", far2l_ReqRecvExpectString)
796 setVMFunction("ExpectNoStrings", far2l_ReqRecvExpectNoStrings)
797 setVMFunction("ExpectNoString", far2l_ReqRecvExpectNoString)
799 setVMFunction("ExpectAppExit", far2l_ExpectExit)
801 setVMFunction("ToggleShift", far2l_ToggleShift)
802 setVMFunction("ToggleLCtrl", far2l_ToggleLCtrl)
803 setVMFunction("ToggleRCtrl", far2l_ToggleRCtrl)
804 setVMFunction("ToggleLAlt", far2l_ToggleLAlt)
805 setVMFunction("ToggleRAlt", far2l_ToggleRAlt)
806 setVMFunction("TypeText", far2l_TypeText)
807 setVMFunction("TypeVK", far2l_TypeVK)
808 setVMFunction("TypeFKey", far2l_TypeFKey)
809 setVMFunction("TypeDigit", far2l_TypeDigit)
810 setVMFunction("TypeAdd", far2l_TypeAdd)
811 setVMFunction("TypeSub", far2l_TypeSub)
812 setVMFunction("TypeMul", far2l_TypeMul)
813 setVMFunction("TypeDiv", far2l_TypeDiv)
814 setVMFunction("TypeSeparator", far2l_TypeSeparator)
815 setVMFunction("TypeDecimal", far2l_TypeDecimal)
817 setVMFunction("TypeEnter", far2l_TypeEnter)
818 setVMFunction("TypeEscape", far2l_TypeEscape)
819 setVMFunction("TypePageUp", far2l_TypePageUp)
820 setVMFunction("TypePageDown", far2l_TypePageDown)
821 setVMFunction("TypeEnd", far2l_TypeEnd)
822 setVMFunction("TypeHome", far2l_TypeHome)
823 setVMFunction("TypeLeft", far2l_TypeLeft)
824 setVMFunction("TypeUp", far2l_TypeUp)
825 setVMFunction("TypeRight", far2l_TypeRight)
826 setVMFunction("TypeDown", far2l_TypeDown)
827 setVMFunction("TypeIns", far2l_TypeIns)
828 setVMFunction("TypeDel", far2l_TypeDel)
829 setVMFunction("TypeBack", far2l_TypeBack)
831 setVMFunction("TTYWrite", tty_Write)
832 setVMFunction("TTYCtrlC", tty_CtrlC)
834 setVMFunction("Log", aux_Log)
835 setVMFunction("Panic", aux_Panic)
837 setVMFunction("RunCmd", aux_RunCmd)
838 setVMFunction("Sleep", aux_Sleep)
840 setVMFunction("WorkDir", aux_WorkDir)
841 setVMFunction("Chmod", aux_Chmod)
842 setVMFunction("Chown", aux_Chown)
843 setVMFunction("Chtimes", aux_Chtimes)
844 setVMFunction("Mkdir", aux_Mkdir)
845 setVMFunction("MkdirTemp", aux_MkdirTemp)
846 setVMFunction("Remove", aux_Remove)
847 setVMFunction("RemoveAll", aux_RemoveAll)
848 setVMFunction("Rename", aux_Rename)
849 setVMFunction("ReadFile", aux_ReadFile)
850 setVMFunction("WriteFile", aux_WriteFile)
851 setVMFunction("Truncate", aux_Truncate)
852 setVMFunction("ReadDir", aux_ReadDir)
853 setVMFunction("Symlink", aux_Symlink)
854 setVMFunction("Readlink", aux_Readlink)
855 setVMFunction("MkdirAll", aux_MkdirAll)
856 setVMFunction("MkdirsAll", aux_MkdirsAll)
857 setVMFunction("Mkfile", aux_Mkfile)
858 setVMFunction("Mkfiles", aux_Mkfiles)
859 setVMFunction("HashPath", aux_HashPath)
860 setVMFunction("HashPathes", aux_HashPathes)
861 setVMFunction("Exists", aux_Exists)
862 setVMFunction("CountExisting", aux_CountExisting)
865 func setVMFunction(name string, value interface{}) {
866 err := g_vm.Set(name, value)
867 if err != nil {
868 aux_Panic(fmt.Sprintf("VMSet(%s): %v", name, err))
872 func main() {
873 var err error
874 arg_ofs:= 1
875 for ;arg_ofs < len(os.Args); arg_ofs++ {
876 if os.Args[arg_ofs] == "-t" && arg_ofs + 1 < len(os.Args) {
877 arg_ofs++
878 v, err := strconv.Atoi(os.Args[arg_ofs])
879 if err != nil || v < 0 { aux_Panic("timeout must be positive integer value") }
880 g_recv_timeout = uint32(v)
881 } else {
882 break
886 if len(os.Args) < arg_ofs + 3 {
887 log.Fatal("Usage: far2l-smoke [-t TIMEOUT_SEC] /path/to/far2l /path/to/work/dir /path/to/test1.js [/path/to/test2.js [/path/to/test3.js ...]]\n")
890 workdir, err := filepath.Abs(os.Args[arg_ofs + 1])
891 if err != nil { log.Fatal(err) }
893 g_far2l_sock = filepath.Join(workdir, "far2l.sock")
894 os.Remove(g_far2l_sock)
895 defer os.Remove(g_far2l_sock)
897 g_socket, err = net.ListenUnixgram("unixgram", &net.UnixAddr{Name:g_far2l_sock, Net:"unixgram"})
898 if err != nil {
899 log.Fatal(err)
902 initVM()
904 g_far2l_bin, err = filepath.Abs(os.Args[arg_ofs])
905 if err != nil {
906 log.Fatal(err)
909 for i := arg_ofs + 2; i < len(os.Args); i++ {
910 fmt.Println("\x1b[1;32m---> Running test: " + os.Args[i] + "\x1b[39;22m")
911 name := filepath.Base(os.Args[i])
912 g_test_workdir = filepath.Join(workdir, strings.TrimSuffix(name, filepath.Ext(name)))
913 runTest(os.Args[i])
917 func saveSnapshotOnExit() {
918 if g_app != nil {
919 f, err := os.Create(g_test_workdir + "/snapshot.txt")
920 if err == nil {
921 f.WriteString(g_app.Snapshot())
926 func runTest(file string) {
927 defer far2l_Close()
928 defer saveSnapshotOnExit()
930 g_lctrl = false
931 g_rctrl = false
932 g_lalt = false
933 g_ralt = false
934 g_shift = false
935 g_calm = false
936 g_last_error = ""
937 data, err := ioutil.ReadFile(file)
938 if err != nil { aux_Panic(err.Error()) }
939 src := string(data)
940 rv, err := g_vm.RunString(src)
941 if err != nil { aux_Panic(err.Error()) }
942 if code := rv.Export().(int64); code != 0 {
943 fmt.Println("[FAILED] Error", code, "from test", file)