package door // Need net, flag for setupSockets import ( "flag" "fmt" "log" "net" "os" "strings" "testing" "time" ) // Should tests not delete the logfiles? // To make use of this, set KEEP_LOGS bool = true and: // go test -c; ./door.test -test.v -test.timeout 40s // Running just one test: // go test -c; ./door.test -test.run DoorCP437 -test.v const KEEP_LOGS bool = false const VERBOSE_TEST bool = false func TestGoto(t *testing.T) { GotoMap := map[string][]int{"\x1b[10;20H": {20, 10}, "\x1b[20;10H": {10, 20}, "\x1b[80;120H": {120, 80}, "\x1b[1;1H": {1, 1}, } for text, code := range GotoMap { gt := Goto(code[0], code[1]) if text != gt { t.Errorf("Goto: Expected %#v (%#v), got %#v", text, code, gt) } } } func TestLogfileFailure(t *testing.T) { tmpFile, err := os.CreateTemp("", "test-door32.sys-*") if err != nil { panic("Cannot create temporary file") } defer func() { if r := recover(); r == nil { t.Error("Init did not panic on logfile error.") } }() // Remember to clean up the file afterwards defer os.Remove(tmpFile.Name()) // This test should fail before we even need sockets var fd int = 0 // Create door32.sys file dfc := DropfileConfig{2, fd, "Test BBSID", 1701, "Real Username", "Handle", 880, 28, 13} err = CreateDoor32File(&dfc, tmpFile) if err != nil { t.Error("tmpFile.WriteString:", err) } err = tmpFile.Close() if err != nil { t.Error("tmpFile.Close:", err) } d := Door{} // If I call d.Init() more then once flag complains about flag redefined. // Reset flags flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError) // preset commandline args so door can init os.Args = []string{"door", "-d", tmpFile.Name()} uid := os.Getuid() if uid == 0 { // Running as root. Use invalid path here. d.Init("/invalid-path/badpath-test") } else { // uid == -1 (windows), this should also fail there. d.Init("/badname-test") } } func TestDetectFail(t *testing.T) { tmpFile, err := os.CreateTemp("", "test-door32.sys-*") if err != nil { panic("Cannot create temporary file") } // Remember to clean up the file afterwards defer os.Remove(tmpFile.Name()) // establish network socket connection to set Comm_handle server, client := setupSockets() // We're not testing closed connections, so: defer server.Close() defer client.Close() // Send nothing var fd int = socket_to_fd(client) defer close_fd(fd) // Create door32.sys file dfc := DropfileConfig{2, fd, "Test BBSID", 1701, "Real Username", "Handle", 880, 28, 13} err = CreateDoor32File(&dfc, tmpFile) if err != nil { t.Error("tmpFile.WriteString:", err) } err = tmpFile.Close() if err != nil { t.Error("tmpFile.Close:", err) } d := Door{ReaderCanClose: true} // Because we're not the only one calling door.Init(), the // door global variables might be from a previous test run. Unicode = false CP437 = false Full_CP437 = false Width = 0 Height = 0 // If I call d.Init() more then once flag complains about flag redefined. // Reset flags flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError) // preset commandline args so door can init os.Args = []string{"door", "-d", tmpFile.Name()} d.Init("menu-test") t.Logf("Init completed.") defer d.Close() // clean up log file // I don't need to. Tests are run in /tmp/go-buildNNNN. // defer os.Remove("menu-test-13.log") if Unicode || CP437 { t.Errorf("Expected FALSE, got Unicode %t CP437 %t", Unicode, CP437) } if Width != 0 || Height != 0 { t.Errorf("Expected 0, 0, got Width %d, Height %d", Width, Height) } t.Logf("Closing server and client...") server.Close() client.Close() // time.Sleep(time.Duration(1) * time.Second) t.Logf("Done.") } // Pass d*Door to test. Otherwise we get a copy of the object passed! // go vet (shows this error/issue) // Generic tests that will be called from CP437 and Unicode routines func InputTests(t *testing.T, server net.Conn, d *Door, mode string) { var err error var buffer string keytest := map[string][]rune{ "\x1b": []rune{0x1b}, "\x0d\x00": []rune{0x0d}, "\x0d\x0a": []rune{0x0d}, "\x0dCQ": []rune{0x0d, 'C', 'Q'}, "\x0dA": []rune{0x0d, 'A'}, "\x0dCAT": []rune{0x0d, 'C', 'A', 'T'}, "\U0001f920\u2615": []rune{'\U0001f920', '\u2615'}, } /* Test input buffer processing. Create a string with unicode that doesn't fit into the entire read buffer. And build the proper response to match. */ // x = number of missing bytes (from unicode symbol). for x := 1; x <= 3; x++ { tosend := strings.Repeat(" ", READ_SIZE-x) + "\U0001f920" receive := []rune(strings.Repeat(" ", READ_SIZE-x)) receive = append(receive, '\U0001f920') keytest[tosend] = receive } keyextest := map[string][]Extended{ "\x00\x50\x00\x48\x00\x4b\x00\x4d": []Extended{DOWN_ARROW, UP_ARROW, LEFT_ARROW, RIGHT_ARROW}, "\x00\x47\x00\x4f\x00\x49\x00\x51": []Extended{HOME, END, PAGE_UP, PAGE_DOWN}, "\x00\x3b\x00\x3c\x00\x3d\x00\x3e": []Extended{F1, F2, F3, F4}, "\x00\x3f\x00\x40\x00\x41\x00\x42": []Extended{F5, F6, F7, F8}, "\x00\x43\x00\x44\x00\x52\x00\x53": []Extended{F9, F10, INSERT, DELETE}, "\x00\x45\x00\x46": []Extended{F11, F12}, "\x1b[A\x1b[B\x1b[C\x1b[D": []Extended{UP_ARROW, DOWN_ARROW, RIGHT_ARROW, LEFT_ARROW}, "\x1b[H\x1b[F\x1b[K\x1b[V\x1b[U": []Extended{HOME, END, END, PAGE_UP, PAGE_DOWN}, "\x1b[5~\x1b[6~": []Extended{PAGE_UP, PAGE_DOWN}, "\x1b[@\x1b[2~\x1b[3~": []Extended{INSERT, INSERT, DELETE}, "\x1b[1~\x1b[4~": []Extended{HOME, END}, "\x1bOP\x1bOQ\x1bOR\x1bOS": []Extended{F1, F2, F3, F4}, "\x1b[15~\x1b[17~\x1b[18~\x1b[19~": []Extended{F5, F6, F7, F8}, "\x1b[20~\x1b[21~\x1b[23~\x1b[24~": []Extended{F9, F10, F11, F12}, } // 50ms causes 3 tries (internal is 200ms, use 210ms). keyWait := time.Duration(210 * time.Millisecond) // Verify input is empty for { input, ex, err := d.WaitKey(keyWait) if err == nil { t.Errorf("Keys: %#v, %#v\n", input, ex) } else { break } } t.Logf("Starting keytest (%s)\n", mode) for send, get := range keytest { if VERBOSE_TEST { t.Logf("Sending %#v: Expect: %#v\n", send, get) } var buffer []byte = []byte(send) _, err = server.Write(buffer) if err != nil { t.Error("server.Write:", err) } time.Sleep(time.Millisecond) var recv []rune = make([]rune, 0) var retries int = 0 for { input, _, err := d.WaitKey(keyWait) if VERBOSE_TEST { t.Logf(">> %#v, %#v\n", input, err) } if err == nil { recv = append(recv, input) if len(recv) != len(get) { continue } break } else { t.Logf("WaitKey Err: %#v\n", err) if len(recv) != len(get) { if retries < 5 { retries++ t.Logf("Retry %d want %d got %d\n", retries, len(get), len(recv)) continue } break } } } if len(recv) != len(get) { t.Errorf("FAILURE: Sent %#v, LEN expected %#v, got %#v", send, get, recv) } else { matches := true for idx, i := range get { if recv[idx] != i { matches = false break } } if !matches { t.Errorf("FAILURE: Sent %#v, MATCH expected %#v, got %#v", send, get, recv) } else { if VERBOSE_TEST { t.Logf("Success.") } } } } t.Logf("Starting keyextest (%s)\n", mode) for send, get := range keyextest { if VERBOSE_TEST { t.Logf("Sending %#v: Expect: %#v\n", send, get) } var buffer []byte = []byte(send) _, err = server.Write(buffer) if err != nil { t.Error("server.Write:", err) } time.Sleep(time.Millisecond) var recv []Extended = make([]Extended, 0) var retries int = 0 for { _, ex, err := d.WaitKey(keyWait) if err == nil { recv = append(recv, ex) if len(recv) != len(get) { continue } break } else { t.Logf("WaitKey err: #%v\n", err) if len(recv) != len(get) { if retries < 5 { retries++ t.Logf("Retry %d want %d got %d\n", retries, len(get), len(recv)) continue } break } } } if len(recv) != len(get) { t.Errorf("Sent %#v, LEN expected %#v, got %#v", send, get, recv) } else { matches := true for idx, i := range get { if recv[idx] != i { matches = false break } } if !matches { t.Errorf("Sent %#v, MATCH expected %#v, got %#v", send, get, recv) } } } clear_socket(server, t) t.Logf("Starting WaitKey timeout (%s)...\n", mode) _, _, timeout := d.WaitKey(keyWait) if timeout == nil { t.Errorf("Expected timeout, got %d / %X", timeout, timeout) } else { t.Logf("Ok! Buffer should be empty! -1 (timeout)") } t.Logf("Starting input test (%s)\n", mode) // Input test buffer = "1234567890\r" _, err = server.Write([]byte(buffer)) if err != nil { t.Error("server.Write:", err) } time.Sleep(time.Millisecond) var input string = d.Input(5) if input != "12345" { t.Errorf("Expected Input(5) = 12345, but got %#v", input) } // read everything var result string = clear_socket(server, t) expected := " \x08\x08\x08\x08\x0812345\x07\x07\x07\x07" if result != expected { expected = " \x08\x08\x08\x08\x0812345\x07\x07\x07\x07\x07" if result != expected { t.Errorf("Buffer Input(5): Expected %#v, got %#v\n", expected, result) } } err = server.SetReadDeadline(time.Time{}) if err != nil { t.Error("server.SetReadDeadLine:", err) } buffer = "12345678\x08\x089\r" _, err = server.Write([]byte(buffer)) if err != nil { t.Error("server.Write:", err) } time.Sleep(time.Millisecond) input = d.Input(5) if input != "1239" { t.Errorf("Expected Input(5) = 1239, but got %#v", input) } buffer = "12\x08\x08\x08987\x00\x48654321\r" _, err = server.Write([]byte(buffer)) if err != nil { t.Error("server.Write:", err) } time.Sleep(time.Millisecond) input = d.Input(5) if input != "98765" { t.Errorf("Expected Input(5) = 98765, but got %#v", input) } t.Logf("Starting cursor pos test (%s)\n", mode) var cpos []CursorPos = []CursorPos{{1, 1}, {50, 5}, {4, 40}, {80, 25}, {160, 55}} for _, pos := range cpos { if VERBOSE_TEST { t.Logf("Sending CursorPos(%d,%d)\n", pos.X, pos.Y) } buffer = CursorReply(pos) _, err := server.Write([]byte(buffer)) if err != nil { t.Error("server.Write:", err) } } for _, pos := range cpos { _, ex, err := d.WaitKey(keyWait) if err != nil { t.Error("WaitKey:", err) continue } if ex != CURSOR { t.Errorf("Expected Extended = CURSOR (was %s)", ex.String()) continue } cp, ok := d.GetCursorPos() if !ok { t.Error("Extended sent CURSOR, but GetCursorPos failed.") continue } if (cp.X != pos.X) || (cp.Y != pos.Y) { t.Errorf("Expected (%d,%d), Got (%d,%d)", pos.X, pos.Y, cp.X, cp.Y) } } t.Logf("Starting Mouse test (%s)\n", mode) var mouse []Mouse = []Mouse{{1, 4, 5}, {4, 4, 5}, {45, 80, 25}, {2, 55, 17}} for _, m := range mouse { if VERBOSE_TEST { t.Logf("Sending Mouse(%d @ %d,%d)\n", m.Button, m.X, m.Y) } buffer = MouseReply(m) _, err = server.Write([]byte(buffer)) if err != nil { t.Error("server.Write:", err) } } for _, pos := range mouse { _, ex, err := d.WaitKey(keyWait) if err != nil { t.Error("WaitKey:", err) continue } if ex != MOUSE { t.Errorf("Expected Extended = MOUSE (was %s)", ex.String()) continue } mpos, ok := d.GetMouse() if !ok { t.Error("Extended sent MOUSE, but GetMouse failed.") continue } if (pos.Button != mpos.Button) || (pos.X != mpos.X) || (pos.Y != mpos.Y) { t.Errorf("Expected (%d @ %d, %d), Got (%d @ %d, %d)", pos.Button, pos.X, pos.Y, mpos.Button, mpos.X, mpos.Y) } } } func TestDoorCP437(t *testing.T) { var tmpFile *os.File var err error // Dropfile tmpFile, err = os.CreateTemp("", "test-door32.sys-*") if err != nil { panic("Cannot create temporary file") } defer os.Remove(tmpFile.Name()) // establish network socket connection to set Comm_handle var server, client net.Conn server, client = setupSockets() // Ok, we have a server socket, and the client socket (that the door would talk to) // CP437 80x25 response buffer := CP437WidthHeight(80, 25) _, err = server.Write([]byte(buffer)) if err != nil { t.Error("server.Write:", err) } time.Sleep(time.Millisecond) // Access Fd (File descriptor) of client for dropfile var fd int = socket_to_fd(client) defer close_fd(fd) // Create door32.sys file var node int = 10 dfc := DropfileConfig{2, fd, "Test BBSID", 1701, "Real Username", "Handle", 880, 28, node} err = CreateDoor32File(&dfc, tmpFile) if err != nil { t.Error("tmpFile.WriteString:", err) } err = tmpFile.Close() if err != nil { t.Error("tmpFile.Close:", err) } d := Door{} // Deprecated: ReaderCanClose: true} // Because we're not the only one calling door.Init(), the // door global variables might be from a previous test run. // Reset back to defaults. Unicode = false CP437 = false Full_CP437 = false Width = 0 Height = 0 // If I call d.Init() more then once flag complains about flag redefined. // Reset flags flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError) // preset commandline args so door can init os.Args = []string{"door", "-d", tmpFile.Name()} d.Init("input-test") defer d.Close() // clean up logfile - (filename is from Init name + node #) if !KEEP_LOGS { defer os.Remove(fmt.Sprintf("input-test-%d.log", node)) } if VERBOSE_TEST { t.Logf("Verify Unicode %dX%d\n", 80, 25) } // Ok! if Unicode { t.Errorf("Unicode is %t", Unicode) } if !CP437 { t.Errorf("Expect CP437 true, is %t", CP437) } if Width != 80 { t.Errorf("Width not 80: %d", Width) } if Height != 25 { t.Errorf("Height not 25: %d", Height) } clear_socket(server, t) InputTests(t, server, &d, "CP437") t.Logf("server close") log.Println("server close") server.Close() time.Sleep(time.Millisecond) keyWait := time.Duration(50 * time.Millisecond) _, _, err = d.WaitKey(keyWait) if err != ErrDisconnected { t.Errorf("Expected ErrDisconnected, got %#v", err) } if !d.Disconnect() { t.Errorf("Disconnected flag shows: %t (should be true)", d.Disconnect()) } _, _, err = d.WaitKey(time.Millisecond) if err != ErrDisconnected { t.Errorf("Expected ErrDisconnected, got %#v", err) } t.Logf("client close") client.Close() time.Sleep(time.Millisecond) t.Logf("Input on closed server and client") var blank string = d.Input(5) if blank != "" { t.Errorf("Input should return blank (hangup).") } _, _, err = d.WaitKey(time.Millisecond) if err != ErrDisconnected { t.Errorf("Expected ErrDisconnected, got %#v", err) } d.Write("\x00") time.Sleep(time.Millisecond) } func TestDoorUnicode(t *testing.T) { var tmpFile *os.File var err error // Dropfile tmpFile, err = os.CreateTemp("", "test-door32.sys-*") if err != nil { panic("Cannot create temporary file") } defer os.Remove(tmpFile.Name()) // establish network socket connection to set Comm_handle var server, client net.Conn server, client = setupSockets() // Ok, we have a server socket, and the client socket (that the door would talk to) // unicode 190x43 response buffer := UnicodeWidthHeight(190, 43) _, err = server.Write([]byte(buffer)) if err != nil { t.Error("server.Write:", err) } time.Sleep(time.Millisecond) // Access Fd (File descriptor) of client for dropfile var fd int = socket_to_fd(client) defer close_fd(fd) // Create door32.sys file var node int = 11 dfc := DropfileConfig{2, fd, "Test BBSID", 1701, "Real Username", "Handle", 880, 28, node} err = CreateDoor32File(&dfc, tmpFile) if err != nil { t.Error("tmpFile.WriteString:", err) } err = tmpFile.Close() if err != nil { t.Error("tmpFile.Close:", err) } d := Door{} // Deprecated: ReaderCanClose: true} // Because we're not the only one calling door.Init(), the // door global variables might be from a previous test run. // Reset back to defaults. Unicode = false CP437 = false Full_CP437 = false Width = 0 Height = 0 // If I call d.Init() more then once flag complains about flag redefined. // Reset flags flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError) // preset commandline args so door can init os.Args = []string{"door", "-d", tmpFile.Name()} d.Init("input-test") defer d.Close() // clean up logfile - (filename is from Init name + node #) if !KEEP_LOGS { defer os.Remove(fmt.Sprintf("input-test-%d.log", node)) } // Ok! if !Unicode { t.Errorf("Unicode not true %t", Unicode) } if Width != 190 { t.Errorf("Width not 190: %d", Width) } if Height != 43 { t.Errorf("Height not 43: %d", Height) } clear_socket(server, t) keyWait := time.Duration(50 * time.Millisecond) InputTests(t, server, &d, "Unicode") buffer = "\U0001f9201234\r" // double wide (2) + 4 = 6, we're inputting 5. _, err = server.Write([]byte(buffer)) if err != nil { t.Error("server.Write:", err) } time.Sleep(time.Millisecond) // Unicode mode can handle unicode input. CP437 mode can't -- really. input := d.Input(5) expect := "\U0001f920123" if input != expect { t.Errorf("Expected Input(5) = %#v, but got %#v", expect, input) } clear_socket(server, t) t.Logf("server close") log.Println("server close") server.Close() time.Sleep(time.Millisecond) _, _, err = d.WaitKey(keyWait) if err != ErrDisconnected { t.Errorf("Expected ErrDisconnected, got %#v", err) } if !d.Disconnect() { t.Errorf("Disconnected flag shows: %t (should be true)", d.Disconnect()) } _, _, err = d.WaitKey(time.Millisecond) if err != ErrDisconnected { t.Errorf("Expected ErrDisconnected, got %#v", err) } t.Logf("client close") client.Close() time.Sleep(time.Millisecond) t.Logf("Input on closed server and client") var blank string = d.Input(5) if blank != "" { t.Errorf("Input should return blank (hangup).") } _, _, err = d.WaitKey(time.Millisecond) if err != ErrDisconnected { t.Errorf("Expected ErrDisconnected, got %#v", err) } d.Write("\x00") time.Sleep(time.Millisecond) }