Use mock for all tests
This commit is contained in:
		
							parent
							
								
									8019e67744
								
							
						
					
					
						commit
						55546487cf
					
				| @ -1,14 +1,10 @@ | ||||
| language: go | ||||
| dist: trusty | ||||
| sudo: required | ||||
| dist: xenial | ||||
| go: | ||||
|   - 1.10.x | ||||
|   - 1.11.x | ||||
| env: | ||||
|   - FTP_SERVER=vsftpd | ||||
|   - FTP_SERVER=proftpd | ||||
| before_install: | ||||
| - sudo $TRAVIS_BUILD_DIR/.travis/prepare.sh "$FTP_SERVER" | ||||
| - sudo sysctl net.ipv6.conf.lo.disable_ipv6=0 | ||||
| - go get github.com/mattn/goveralls | ||||
| - go get github.com/golang/lint/golint | ||||
|  | ||||
| @ -1,19 +0,0 @@ | ||||
| #!/bin/sh -e | ||||
| 
 | ||||
| case "$1" in | ||||
|   proftpd) | ||||
|     mkdir -p /etc/proftpd/conf.d/ | ||||
|     cp $TRAVIS_BUILD_DIR/.travis/proftpd.conf /etc/proftpd/conf.d/ | ||||
|     ;; | ||||
|   vsftpd) | ||||
|     cp $TRAVIS_BUILD_DIR/.travis/vsftpd.conf /etc/vsftpd.conf | ||||
|     ;; | ||||
|   *) | ||||
|     echo "unknown software: $1" | ||||
|     exit 1 | ||||
| esac | ||||
| 
 | ||||
| mkdir --mode 0777 -p /var/ftp/incoming | ||||
| 
 | ||||
| apt-get update -qq | ||||
| apt-get install -qq "$1" | ||||
| @ -1,9 +0,0 @@ | ||||
| <Anonymous /var/ftp> | ||||
|   User        ftp | ||||
|   Group       nogroup | ||||
|   MaxClients  2 | ||||
|   # We want clients to be able to login with "anonymous" as well as "ftp" | ||||
|   UserAlias   anonymous ftp | ||||
| 
 | ||||
|   RequireValidShell  off | ||||
| </Anonymous> | ||||
| @ -1,15 +0,0 @@ | ||||
| # Used by Travis CI | ||||
| 
 | ||||
| listen=NO | ||||
| listen_ipv6=YES | ||||
| 
 | ||||
| write_enable=YES | ||||
| dirmessage_enable=YES | ||||
| secure_chroot_dir=/var/run/vsftpd/empty | ||||
| 
 | ||||
| anonymous_enable=YES | ||||
| anon_root=/var/ftp | ||||
| anon_upload_enable=YES | ||||
| anon_mkdir_write_enable=YES | ||||
| anon_other_write_enable=YES | ||||
| anon_umask=022 | ||||
| @ -1,107 +0,0 @@ | ||||
| package ftp | ||||
| 
 | ||||
| import ( | ||||
| 	"net" | ||||
| 	"net/textproto" | ||||
| 	"reflect" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| 	"testing" | ||||
| ) | ||||
| 
 | ||||
| type ftpMock struct { | ||||
| 	listener net.Listener | ||||
| 	commands []string // list of received commands | ||||
| 	sync.WaitGroup | ||||
| } | ||||
| 
 | ||||
| func newFtpMock(t *testing.T, addresss string) *ftpMock { | ||||
| 	var err error | ||||
| 	mock := &ftpMock{} | ||||
| 	mock.listener, err = net.Listen("tcp", addresss) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	go func() { | ||||
| 		// Listen for an incoming connection. | ||||
| 		conn, err := mock.listener.Accept() | ||||
| 		if err != nil { | ||||
| 			t.Fatal(err) | ||||
| 		} | ||||
| 
 | ||||
| 		mock.Add(1) | ||||
| 		defer mock.Done() | ||||
| 		defer conn.Close() | ||||
| 
 | ||||
| 		proto := textproto.NewConn(conn) | ||||
| 		proto.Writer.PrintfLine("220 FTP Server ready.") | ||||
| 
 | ||||
| 		for { | ||||
| 			command, _ := proto.ReadLine() | ||||
| 
 | ||||
| 			// Strip the arguments | ||||
| 			if i := strings.Index(command, " "); i > 0 { | ||||
| 				command = command[:i] | ||||
| 			} | ||||
| 
 | ||||
| 			// Append to list of received commands | ||||
| 			mock.commands = append(mock.commands, command) | ||||
| 
 | ||||
| 			// At least one command must have a multiline response | ||||
| 			switch command { | ||||
| 			case "FEAT": | ||||
| 				proto.Writer.PrintfLine("211-Features:\r\nFEAT\r\nPASV\r\nSIZE\r\n211 End") | ||||
| 			case "USER": | ||||
| 				proto.Writer.PrintfLine("331 Please send your password") | ||||
| 			case "PASS": | ||||
| 				proto.Writer.PrintfLine("230-Hey,\r\nWelcome to my FTP\r\n230 Access granted") | ||||
| 			case "TYPE": | ||||
| 				proto.Writer.PrintfLine("200 Type set ok") | ||||
| 			case "QUIT": | ||||
| 				proto.Writer.PrintfLine("221 Goodbye.") | ||||
| 				return | ||||
| 			default: | ||||
| 				t.Fatal("unknown command:", command) | ||||
| 			} | ||||
| 		} | ||||
| 	}() | ||||
| 
 | ||||
| 	return mock | ||||
| } | ||||
| 
 | ||||
| // Closes the listening socket | ||||
| func (mock *ftpMock) Close() { | ||||
| 	mock.listener.Close() | ||||
| } | ||||
| 
 | ||||
| // ftp.mozilla.org uses multiline 220 response | ||||
| func TestMultiline(t *testing.T) { | ||||
| 	if testing.Short() { | ||||
| 		t.Skip("skipping test in short mode.") | ||||
| 	} | ||||
| 
 | ||||
| 	address := "localhost:2121" | ||||
| 	mock := newFtpMock(t, address) | ||||
| 	defer mock.Close() | ||||
| 
 | ||||
| 	c, err := Dial(address) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	err = c.Login("anonymous", "anonymous") | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	c.Quit() | ||||
| 
 | ||||
| 	// Wait for the connection to close | ||||
| 	mock.Wait() | ||||
| 
 | ||||
| 	expected := []string{"FEAT", "USER", "PASS", "TYPE", "QUIT"} | ||||
| 	if !reflect.DeepEqual(mock.commands, expected) { | ||||
| 		t.Fatal("unexpected sequence of commands:", mock.commands, "expected:", expected) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										295
									
								
								client_test.go
									
									
									
									
									
								
							
							
						
						
									
										295
									
								
								client_test.go
									
									
									
									
									
								
							| @ -23,21 +23,15 @@ func TestConnEPSV(t *testing.T) { | ||||
| } | ||||
| 
 | ||||
| func testConn(t *testing.T, disableEPSV bool) { | ||||
| 	if testing.Short() { | ||||
| 		t.Skip("skipping test in short mode.") | ||||
| 	} | ||||
| 
 | ||||
| 	c, err := DialTimeout("localhost:21", 5*time.Second) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	mock, c := openConn(t, "127.0.0.1") | ||||
| 
 | ||||
| 	if disableEPSV { | ||||
| 		delete(c.features, "EPSV") | ||||
| 		c.DisableEPSV = true | ||||
| 	} | ||||
| 
 | ||||
| 	err = c.Login("anonymous", "anonymous") | ||||
| 	err := c.Login("anonymous", "anonymous") | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| @ -52,6 +46,15 @@ func testConn(t *testing.T, disableEPSV bool) { | ||||
| 		t.Error(err) | ||||
| 	} | ||||
| 
 | ||||
| 	dir, err := c.CurrentDir() | ||||
| 	if err != nil { | ||||
| 		t.Error(err) | ||||
| 	} else { | ||||
| 		if dir != "/incoming" { | ||||
| 			t.Error("Wrong dir: " + dir) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	data := bytes.NewBufferString(testData) | ||||
| 	err = c.Stor("test", data) | ||||
| 	if err != nil { | ||||
| @ -115,26 +118,12 @@ func testConn(t *testing.T, disableEPSV bool) { | ||||
| 		r.Close() | ||||
| 	} | ||||
| 
 | ||||
| 	fileSize, err := c.FileSize("tset") | ||||
| 	fileSize, err := c.FileSize("magic-file") | ||||
| 	if err != nil { | ||||
| 		t.Error(err) | ||||
| 	} | ||||
| 	if fileSize != 14 { | ||||
| 		t.Errorf("file size %q, expected %q", fileSize, 14) | ||||
| 	} | ||||
| 
 | ||||
| 	data = bytes.NewBufferString("") | ||||
| 	err = c.Stor("tset", data) | ||||
| 	if err != nil { | ||||
| 		t.Error(err) | ||||
| 	} | ||||
| 
 | ||||
| 	fileSize, err = c.FileSize("tset") | ||||
| 	if err != nil { | ||||
| 		t.Error(err) | ||||
| 	} | ||||
| 	if fileSize != 0 { | ||||
| 		t.Errorf("file size %q, expected %q", fileSize, 0) | ||||
| 	if fileSize != 42 { | ||||
| 		t.Errorf("file size %q, expected %q", fileSize, 42) | ||||
| 	} | ||||
| 
 | ||||
| 	_, err = c.FileSize("not-found") | ||||
| @ -157,15 +146,6 @@ func testConn(t *testing.T, disableEPSV bool) { | ||||
| 		t.Error(err) | ||||
| 	} | ||||
| 
 | ||||
| 	dir, err := c.CurrentDir() | ||||
| 	if err != nil { | ||||
| 		t.Error(err) | ||||
| 	} else { | ||||
| 		if dir != "/incoming/"+testDir { | ||||
| 			t.Error("Wrong dir: " + dir) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	err = c.ChangeDirToParent() | ||||
| 	if err != nil { | ||||
| 		t.Error(err) | ||||
| @ -195,7 +175,12 @@ func testConn(t *testing.T, disableEPSV bool) { | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	c.Quit() | ||||
| 	if err := c.Quit(); err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Wait for the connection to close | ||||
| 	mock.Wait() | ||||
| 
 | ||||
| 	err = c.NoOp() | ||||
| 	if err == nil { | ||||
| @ -203,41 +188,23 @@ func testConn(t *testing.T, disableEPSV bool) { | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestConnIPv6(t *testing.T) { | ||||
| 	if testing.Short() { | ||||
| 		t.Skip("skipping test in short mode.") | ||||
| 	} | ||||
| 
 | ||||
| 	c, err := DialTimeout("[::1]:21", 5*time.Second) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	err = c.Login("anonymous", "anonymous") | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	_, err = c.List(".") | ||||
| 	if err != nil { | ||||
| 		t.Error(err) | ||||
| 	} | ||||
| 
 | ||||
| 	c.Quit() | ||||
| } | ||||
| 
 | ||||
| // TestConnect tests the legacy Connect function | ||||
| func TestConnect(t *testing.T) { | ||||
| 	if testing.Short() { | ||||
| 		t.Skip("skipping test in short mode.") | ||||
| 	mock, err := newFtpMock(t, "127.0.0.1") | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	defer mock.Close() | ||||
| 
 | ||||
| 	c, err := Connect("localhost:21") | ||||
| 	c, err := Connect(mock.Addr()) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	c.Quit() | ||||
| 	if err := c.Quit(); err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	mock.Wait() | ||||
| } | ||||
| 
 | ||||
| func TestTimeout(t *testing.T) { | ||||
| @ -253,11 +220,13 @@ func TestTimeout(t *testing.T) { | ||||
| } | ||||
| 
 | ||||
| func TestWrongLogin(t *testing.T) { | ||||
| 	if testing.Short() { | ||||
| 		t.Skip("skipping test in short mode.") | ||||
| 	mock, err := newFtpMock(t, "127.0.0.1") | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	defer mock.Close() | ||||
| 
 | ||||
| 	c, err := DialTimeout("localhost:21", 5*time.Second) | ||||
| 	c, err := DialTimeout(mock.Addr(), 5*time.Second) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| @ -270,189 +239,49 @@ func TestWrongLogin(t *testing.T) { | ||||
| } | ||||
| 
 | ||||
| func TestDeleteDirRecur(t *testing.T) { | ||||
| 	if testing.Short() { | ||||
| 		t.Skip("skipping test in short mode.") | ||||
| 	} | ||||
| 	c, err := DialTimeout("localhost:21", 5*time.Second) | ||||
| 	mock, c := openConn(t, "127.0.0.1") | ||||
| 
 | ||||
| 	err := c.RemoveDirRecur("testDir") | ||||
| 	if err != nil { | ||||
| 		t.Error(err) | ||||
| 	} | ||||
| 
 | ||||
| 	if err := c.Quit(); err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	err = c.Login("anonymous", "anonymous") | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	// Wait for the connection to close | ||||
| 	mock.Wait() | ||||
| } | ||||
| 
 | ||||
| 	err = c.NoOp() | ||||
| 	if err != nil { | ||||
| 		t.Error(err) | ||||
| 	} | ||||
| // func TestFileDeleteDirRecur(t *testing.T) { | ||||
| // 	mock, c := openConn(t, "127.0.0.1") | ||||
| 
 | ||||
| 	err = c.ChangeDir("incoming") | ||||
| 	if err != nil { | ||||
| 		t.Error(err) | ||||
| 	} | ||||
| // 	err := c.RemoveDirRecur("testFile") | ||||
| // 	if err == nil { | ||||
| // 		t.Fatal("expected error got nil") | ||||
| // 	} | ||||
| 
 | ||||
| 	err = c.MakeDir("testDir") | ||||
| 	if err != nil { | ||||
| 		t.Error(err) | ||||
| 	} | ||||
| // 	if err := c.Quit(); err != nil { | ||||
| // 		t.Fatal(err) | ||||
| // 	} | ||||
| 
 | ||||
| 	err = c.ChangeDir("testDir") | ||||
| 	if err != nil { | ||||
| 		t.Error(err) | ||||
| 	} | ||||
| 
 | ||||
| 	err = c.MakeDir("anotherDir") | ||||
| 	if err != nil { | ||||
| 		t.Error(err) | ||||
| 	} | ||||
| 
 | ||||
| 	data := bytes.NewBufferString("test text") | ||||
| 	err = c.Stor("fileTest", data) | ||||
| 	if err != nil { | ||||
| 		t.Error(err) | ||||
| 	} | ||||
| 
 | ||||
| 	err = c.ChangeDirToParent() | ||||
| 	if err != nil { | ||||
| 		t.Error(err) | ||||
| 	} | ||||
| 	err = c.RemoveDirRecur("testDir") | ||||
| 	if err != nil { | ||||
| 		t.Error(err) | ||||
| 	} | ||||
| 	dir, err := c.CurrentDir() | ||||
| 	if err != nil { | ||||
| 		t.Error(err) | ||||
| 	} else { | ||||
| 		if dir != "/incoming" { | ||||
| 			t.Error("Wrong dir: " + dir) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	err = c.ChangeDir("testDir") | ||||
| 	if err == nil { | ||||
| 		t.Fatal("expected error, got nil") | ||||
| 	} | ||||
| 
 | ||||
| 	err = c.Logout() | ||||
| 	if err != nil { | ||||
| 		if protoErr := err.(*textproto.Error); protoErr != nil { | ||||
| 			if protoErr.Code != StatusNotImplemented { | ||||
| 				t.Error(err) | ||||
| 			} | ||||
| 		} else { | ||||
| 			t.Error(err) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	c.Quit() | ||||
| } | ||||
| 
 | ||||
| func TestFileDeleteDirRecur(t *testing.T) { | ||||
| 	if testing.Short() { | ||||
| 		t.Skip("skipping test in short mode.") | ||||
| 	} | ||||
| 
 | ||||
| 	c, err := DialTimeout("localhost:21", 5*time.Second) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	err = c.Login("anonymous", "anonymous") | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	err = c.ChangeDir("incoming") | ||||
| 	if err != nil { | ||||
| 		t.Error(err) | ||||
| 	} | ||||
| 
 | ||||
| 	data := bytes.NewBufferString(testData) | ||||
| 	err = c.Stor("testFile", data) | ||||
| 	if err != nil { | ||||
| 		t.Error(err) | ||||
| 	} | ||||
| 
 | ||||
| 	err = c.RemoveDirRecur("testFile") | ||||
| 	if err == nil { | ||||
| 		t.Fatal("expected error got nill") | ||||
| 	} | ||||
| 
 | ||||
| 	dir, err := c.CurrentDir() | ||||
| 	if err != nil { | ||||
| 		t.Error(err) | ||||
| 	} else { | ||||
| 		if dir != "/incoming" { | ||||
| 			t.Error("Wrong dir: " + dir) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	err = c.Delete("testFile") | ||||
| 	if err != nil { | ||||
| 		t.Error(err) | ||||
| 	} | ||||
| 
 | ||||
| 	err = c.Logout() | ||||
| 	if err != nil { | ||||
| 		if protoErr := err.(*textproto.Error); protoErr != nil { | ||||
| 			if protoErr.Code != StatusNotImplemented { | ||||
| 				t.Error(err) | ||||
| 			} | ||||
| 		} else { | ||||
| 			t.Error(err) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	c.Quit() | ||||
| } | ||||
| // 	// Wait for the connection to close | ||||
| // 	mock.Wait() | ||||
| // } | ||||
| 
 | ||||
| func TestMissingFolderDeleteDirRecur(t *testing.T) { | ||||
| 	if testing.Short() { | ||||
| 		t.Skip("skipping test in short mode.") | ||||
| 	} | ||||
| 	mock, c := openConn(t, "127.0.0.1") | ||||
| 
 | ||||
| 	c, err := DialTimeout("localhost:21", 5*time.Second) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	err = c.Login("anonymous", "anonymous") | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	err = c.ChangeDir("incoming") | ||||
| 	if err != nil { | ||||
| 		t.Error(err) | ||||
| 	} | ||||
| 
 | ||||
| 	err = c.RemoveDirRecur("test") | ||||
| 	err := c.RemoveDirRecur("missing-dir") | ||||
| 	if err == nil { | ||||
| 		t.Fatal("expected error got nill") | ||||
| 		t.Fatal("expected error got nil") | ||||
| 	} | ||||
| 
 | ||||
| 	dir, err := c.CurrentDir() | ||||
| 	if err != nil { | ||||
| 		t.Error(err) | ||||
| 	} else { | ||||
| 		if dir != "/incoming" { | ||||
| 			t.Error("Wrong dir: " + dir) | ||||
| 		} | ||||
| 	if err := c.Quit(); err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	err = c.Logout() | ||||
| 	if err != nil { | ||||
| 		if protoErr := err.(*textproto.Error); protoErr != nil { | ||||
| 			if protoErr.Code != StatusNotImplemented { | ||||
| 				t.Error(err) | ||||
| 			} | ||||
| 		} else { | ||||
| 			t.Error(err) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	c.Quit() | ||||
| 	// Wait for the connection to close | ||||
| 	mock.Wait() | ||||
| } | ||||
|  | ||||
							
								
								
									
										335
									
								
								conn_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										335
									
								
								conn_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,335 @@ | ||||
| package ftp | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"io" | ||||
| 	"io/ioutil" | ||||
| 	"net" | ||||
| 	"net/textproto" | ||||
| 	"reflect" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| 	"testing" | ||||
| ) | ||||
| 
 | ||||
| type ftpMock struct { | ||||
| 	address  string | ||||
| 	listener *net.TCPListener | ||||
| 	proto    *textproto.Conn | ||||
| 	commands []string // list of received commands | ||||
| 	rest     int | ||||
| 	dataConn *mockDataConn | ||||
| 	sync.WaitGroup | ||||
| } | ||||
| 
 | ||||
| // newFtpMock returns a mock implementation of a FTP server | ||||
| // For simplication, a mock instance only accepts a signle connection and terminates afer | ||||
| func newFtpMock(t *testing.T, address string) (*ftpMock, error) { | ||||
| 	var err error | ||||
| 	mock := &ftpMock{address: address} | ||||
| 
 | ||||
| 	l, err := net.Listen("tcp", address+":0") | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	tcpListener, ok := l.(*net.TCPListener) | ||||
| 	if !ok { | ||||
| 		return nil, errors.New("listener is not a net.TCPListener") | ||||
| 	} | ||||
| 	mock.listener = tcpListener | ||||
| 
 | ||||
| 	go mock.listen(t) | ||||
| 
 | ||||
| 	return mock, nil | ||||
| } | ||||
| 
 | ||||
| func (mock *ftpMock) listen(t *testing.T) { | ||||
| 	// Listen for an incoming connection. | ||||
| 	conn, err := mock.listener.Accept() | ||||
| 	if err != nil { | ||||
| 		t.Errorf("can not accept: %s", err) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	// Do not accept incoming connections anymore | ||||
| 	mock.listener.Close() | ||||
| 
 | ||||
| 	mock.Add(1) | ||||
| 	defer mock.Done() | ||||
| 	defer conn.Close() | ||||
| 
 | ||||
| 	mock.proto = textproto.NewConn(conn) | ||||
| 	mock.proto.Writer.PrintfLine("220 FTP Server ready.") | ||||
| 
 | ||||
| 	for { | ||||
| 		fullCommand, _ := mock.proto.ReadLine() | ||||
| 
 | ||||
| 		cmdParts := strings.Split(fullCommand, " ") | ||||
| 
 | ||||
| 		// Append to list of received commands | ||||
| 		mock.commands = append(mock.commands, cmdParts[0]) | ||||
| 
 | ||||
| 		// At least one command must have a multiline response | ||||
| 		switch cmdParts[0] { | ||||
| 		case "FEAT": | ||||
| 			mock.proto.Writer.PrintfLine("211-Features:\r\n FEAT\r\n PASV\r\n EPSV\r\n SIZE\r\n211 End") | ||||
| 		case "USER": | ||||
| 			if cmdParts[1] == "anonymous" { | ||||
| 				mock.proto.Writer.PrintfLine("331 Please send your password") | ||||
| 			} else { | ||||
| 				mock.proto.Writer.PrintfLine("530 This FTP server is anonymous only") | ||||
| 			} | ||||
| 		case "PASS": | ||||
| 			mock.proto.Writer.PrintfLine("230-Hey,\r\nWelcome to my FTP\r\n230 Access granted") | ||||
| 		case "TYPE": | ||||
| 			mock.proto.Writer.PrintfLine("200 Type set ok") | ||||
| 		case "CWD": | ||||
| 			if cmdParts[1] == "missing-dir" { | ||||
| 				mock.proto.Writer.PrintfLine("550 %s: No such file or directory", cmdParts[1]) | ||||
| 			} else { | ||||
| 				mock.proto.Writer.PrintfLine("250 Directory successfully changed.") | ||||
| 			} | ||||
| 		case "DELE": | ||||
| 			mock.proto.Writer.PrintfLine("250 File successfully removed.") | ||||
| 		case "MKD": | ||||
| 			mock.proto.Writer.PrintfLine("257 Directory successfully created.") | ||||
| 		case "RMD": | ||||
| 			if cmdParts[1] == "missing-dir" { | ||||
| 				mock.proto.Writer.PrintfLine("550 No such file or directory") | ||||
| 			} else { | ||||
| 				mock.proto.Writer.PrintfLine("250 Directory successfully removed.") | ||||
| 			} | ||||
| 		case "PWD": | ||||
| 			mock.proto.Writer.PrintfLine("257 \"/incoming\"") | ||||
| 		case "CDUP": | ||||
| 			mock.proto.Writer.PrintfLine("250 CDUP command successful") | ||||
| 		case "SIZE": | ||||
| 			if cmdParts[1] == "magic-file" { | ||||
| 				mock.proto.Writer.PrintfLine("213 42") | ||||
| 			} else { | ||||
| 				mock.proto.Writer.PrintfLine("550 Could not get file size.") | ||||
| 			} | ||||
| 		case "PASV": | ||||
| 			p, err := mock.listenDataConn() | ||||
| 			if err != nil { | ||||
| 				mock.proto.Writer.PrintfLine("451 %s.", err) | ||||
| 				break | ||||
| 			} | ||||
| 
 | ||||
| 			p1 := int(p / 256) | ||||
| 			p2 := p % 256 | ||||
| 
 | ||||
| 			mock.proto.Writer.PrintfLine("227 Entering Passive Mode (127,0,0,1,%d,%d).", p1, p2) | ||||
| 		case "EPSV": | ||||
| 			p, err := mock.listenDataConn() | ||||
| 			if err != nil { | ||||
| 				mock.proto.Writer.PrintfLine("451 %s.", err) | ||||
| 				break | ||||
| 			} | ||||
| 			mock.proto.Writer.PrintfLine("229 Entering Extended Passive Mode (|||%d|)", p) | ||||
| 		case "STOR": | ||||
| 			if mock.dataConn == nil { | ||||
| 				mock.proto.Writer.PrintfLine("425 Unable to build data connection: Connection refused") | ||||
| 				break | ||||
| 			} | ||||
| 			mock.proto.Writer.PrintfLine("150 please send") | ||||
| 			mock.recvDataConn() | ||||
| 		case "LIST": | ||||
| 			if mock.dataConn == nil { | ||||
| 				mock.proto.Writer.PrintfLine("425 Unable to build data connection: Connection refused") | ||||
| 				break | ||||
| 			} | ||||
| 
 | ||||
| 			mock.dataConn.Wait() | ||||
| 			mock.proto.Writer.PrintfLine("150 Opening ASCII mode data connection for file list") | ||||
| 			mock.dataConn.conn.Write([]byte("-rw-r--r--   1 ftp      wheel           0 Jan 29 10:29 lo")) | ||||
| 			mock.proto.Writer.PrintfLine("226 Transfer complete") | ||||
| 			mock.closeDataConn() | ||||
| 		case "NLST": | ||||
| 			if mock.dataConn == nil { | ||||
| 				mock.proto.Writer.PrintfLine("425 Unable to build data connection: Connection refused") | ||||
| 				break | ||||
| 			} | ||||
| 
 | ||||
| 			mock.dataConn.Wait() | ||||
| 			mock.proto.Writer.PrintfLine("150 Opening ASCII mode data connection for file list") | ||||
| 			mock.dataConn.conn.Write([]byte("/incoming")) | ||||
| 			mock.proto.Writer.PrintfLine("226 Transfer complete") | ||||
| 			mock.closeDataConn() | ||||
| 		case "RETR": | ||||
| 			if mock.dataConn == nil { | ||||
| 				mock.proto.Writer.PrintfLine("425 Unable to build data connection: Connection refused") | ||||
| 				break | ||||
| 			} | ||||
| 
 | ||||
| 			mock.dataConn.Wait() | ||||
| 			mock.proto.Writer.PrintfLine("150 Opening ASCII mode data connection for file list") | ||||
| 			mock.dataConn.conn.Write([]byte(testData[mock.rest:])) | ||||
| 			mock.rest = 0 | ||||
| 			mock.proto.Writer.PrintfLine("226 Transfer complete") | ||||
| 			mock.closeDataConn() | ||||
| 		case "RNFR": | ||||
| 			mock.proto.Writer.PrintfLine("350 File or directory exists, ready for destination name") | ||||
| 		case "RNTO": | ||||
| 			mock.proto.Writer.PrintfLine("250 Rename successful") | ||||
| 		case "REST": | ||||
| 			if len(cmdParts) != 2 { | ||||
| 				mock.proto.Writer.PrintfLine("500 wrong number of arguments") | ||||
| 				break | ||||
| 			} | ||||
| 			rest, err := strconv.Atoi(cmdParts[1]) | ||||
| 			if err != nil { | ||||
| 				mock.proto.Writer.PrintfLine("500 REST: %s", err) | ||||
| 				break | ||||
| 			} | ||||
| 			mock.rest = rest | ||||
| 			mock.proto.Writer.PrintfLine("350 Restarting at %s. Send STORE or RETRIEVE to initiate transfer", cmdParts[1]) | ||||
| 		case "NOOP": | ||||
| 			mock.proto.Writer.PrintfLine("200 NOOP ok.") | ||||
| 		case "REIN": | ||||
| 			mock.proto.Writer.PrintfLine("220 Logged out") | ||||
| 		case "QUIT": | ||||
| 			mock.proto.Writer.PrintfLine("221 Goodbye.") | ||||
| 			return | ||||
| 		default: | ||||
| 			mock.proto.Writer.PrintfLine("500 Unknown command %s.", cmdParts[0]) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (mock *ftpMock) closeDataConn() (err error) { | ||||
| 	if mock.dataConn != nil { | ||||
| 		err = mock.dataConn.Close() | ||||
| 		mock.dataConn = nil | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| type mockDataConn struct { | ||||
| 	listener *net.TCPListener | ||||
| 	conn     net.Conn | ||||
| 	// WaitGroup is done when conn is accepted and stored | ||||
| 	sync.WaitGroup | ||||
| } | ||||
| 
 | ||||
| func (d *mockDataConn) Close() (err error) { | ||||
| 	if d.listener != nil { | ||||
| 		err = d.listener.Close() | ||||
| 	} | ||||
| 	if d.conn != nil { | ||||
| 		err = d.conn.Close() | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| func (mock *ftpMock) listenDataConn() (int64, error) { | ||||
| 	mock.closeDataConn() | ||||
| 
 | ||||
| 	l, err := net.Listen("tcp", mock.address+":0") | ||||
| 	if err != nil { | ||||
| 		return 0, err | ||||
| 	} | ||||
| 
 | ||||
| 	tcpListener, ok := l.(*net.TCPListener) | ||||
| 	if !ok { | ||||
| 		return 0, errors.New("listener is not a net.TCPListener") | ||||
| 	} | ||||
| 
 | ||||
| 	addr := tcpListener.Addr().String() | ||||
| 
 | ||||
| 	_, port, err := net.SplitHostPort(addr) | ||||
| 	if err != nil { | ||||
| 		return 0, err | ||||
| 	} | ||||
| 
 | ||||
| 	p, err := strconv.ParseInt(port, 10, 32) | ||||
| 	if err != nil { | ||||
| 		return 0, err | ||||
| 	} | ||||
| 
 | ||||
| 	dataConn := &mockDataConn{listener: tcpListener} | ||||
| 	dataConn.Add(1) | ||||
| 
 | ||||
| 	go func() { | ||||
| 		// Listen for an incoming connection. | ||||
| 		conn, err := dataConn.listener.Accept() | ||||
| 		if err != nil { | ||||
| 			// t.Errorf("can not accept: %s", err) | ||||
| 			return | ||||
| 		} | ||||
| 
 | ||||
| 		dataConn.conn = conn | ||||
| 		dataConn.Done() | ||||
| 	}() | ||||
| 
 | ||||
| 	mock.dataConn = dataConn | ||||
| 	return p, nil | ||||
| } | ||||
| 
 | ||||
| func (mock *ftpMock) recvDataConn() { | ||||
| 	mock.dataConn.Wait() | ||||
| 	io.Copy(ioutil.Discard, mock.dataConn.conn) | ||||
| 	mock.proto.Writer.PrintfLine("226 Transfer Complete") | ||||
| 	mock.closeDataConn() | ||||
| } | ||||
| 
 | ||||
| func (mock *ftpMock) Addr() string { | ||||
| 	return mock.listener.Addr().String() | ||||
| } | ||||
| 
 | ||||
| // Closes the listening socket | ||||
| func (mock *ftpMock) Close() { | ||||
| 	mock.listener.Close() | ||||
| } | ||||
| 
 | ||||
| // Helper to return a client connected to a mock server | ||||
| func openConn(t *testing.T, addr string) (*ftpMock, *ServerConn) { | ||||
| 	mock, err := newFtpMock(t, addr) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	defer mock.Close() | ||||
| 
 | ||||
| 	c, err := Dial(mock.Addr()) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	err = c.Login("anonymous", "anonymous") | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	return mock, c | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| // Helper to close a client connected to a mock server | ||||
| func closeConn(t *testing.T, mock *ftpMock, c *ServerConn, commands []string) { | ||||
| 	expected := []string{"FEAT", "USER", "PASS", "TYPE"} | ||||
| 	expected = append(expected, commands...) | ||||
| 	expected = append(expected, "QUIT") | ||||
| 
 | ||||
| 	if err := c.Quit(); err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Wait for the connection to close | ||||
| 	mock.Wait() | ||||
| 
 | ||||
| 	if !reflect.DeepEqual(mock.commands, expected) { | ||||
| 		t.Fatal("unexpected sequence of commands:", mock.commands, "expected:", expected) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestConn4(t *testing.T) { | ||||
| 	mock, c := openConn(t, "127.0.0.1") | ||||
| 	closeConn(t, mock, c, nil) | ||||
| } | ||||
| 
 | ||||
| func TestConn6(t *testing.T) { | ||||
| 	mock, c := openConn(t, "[::1]") | ||||
| 	closeConn(t, mock, c, nil) | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user