@@ -30,6 +30,14 @@ type FTPTestContainer struct {
3030
3131// NewFTPTestContainer uses delfer/alpine-ftp-server, minimal env vars, fixed host port mapping syntax.
3232func NewFTPTestContainer (ctx context.Context , t * testing.T ) * FTPTestContainer {
33+ fc , err := NewFTPTestContainerE (ctx )
34+ require .NoError (t , err )
35+ return fc
36+ }
37+
38+ // NewFTPTestContainerE uses delfer/alpine-ftp-server, minimal env vars, fixed host port mapping syntax.
39+ // Returns error instead of using require.NoError, suitable for TestMain usage.
40+ func NewFTPTestContainerE (ctx context.Context ) (* FTPTestContainer , error ) {
3341 const (
3442 defaultUser = "ftpuser"
3543 defaultPassword = "ftppass"
@@ -38,9 +46,6 @@ func NewFTPTestContainer(ctx context.Context, t *testing.T) *FTPTestContainer {
3846 fixedHostControlPort = "2121"
3947 )
4048
41- // set up logging for testcontainers if the appropriate API is available
42- t .Logf ("Setting up FTP test container" )
43-
4449 pasvPortRangeContainer := fmt .Sprintf ("%s-%s" , pasvMinPort , pasvMaxPort )
4550 pasvPortRangeHost := fmt .Sprintf ("%s-%s" , pasvMinPort , pasvMaxPort ) // map 1:1
4651 exposedPortsWithBinding := []string {
@@ -49,7 +54,6 @@ func NewFTPTestContainer(ctx context.Context, t *testing.T) *FTPTestContainer {
4954 }
5055
5156 imageName := "delfer/alpine-ftp-server:latest"
52- t .Logf ("Using FTP server image: %s" , imageName )
5357
5458 req := testcontainers.ContainerRequest {
5559 Image : imageName ,
@@ -60,34 +64,33 @@ func NewFTPTestContainer(ctx context.Context, t *testing.T) *FTPTestContainer {
6064 WaitingFor : wait .ForListeningPort (nat .Port ("21/tcp" )).WithStartupTimeout (2 * time .Minute ),
6165 }
6266
63- t .Logf ("creating FTP container using %s (minimal env vars, fixed host port %s)..." , imageName , fixedHostControlPort )
6467 container , err := testcontainers .GenericContainer (ctx , testcontainers.GenericContainerRequest {
6568 ContainerRequest : req ,
6669 Started : true ,
6770 })
68- // create the container instance to use its methods
69- ftpContainer := & FTPTestContainer {}
70-
71- // error handling with detailed logging for container startup issues
7271 if err != nil {
73- ftpContainer . logContainerError ( ctx , t , container , err , imageName )
72+ return nil , fmt . Errorf ( "failed to create ftp container: %w" , err )
7473 }
75- t .Logf ("FTP container created and started (ID: %s)" , container .GetContainerID ())
7674
7775 host , err := container .Host (ctx )
78- require .NoError (t , err , "Failed to get container host" )
76+ if err != nil {
77+ _ = container .Terminate (ctx )
78+ return nil , fmt .Errorf ("failed to get container host: %w" , err )
79+ }
7980
8081 // since we requested a fixed port, construct the nat.Port struct directly
8182 // we still call MappedPort just to ensure the container is properly exposing *something* for port 21
82- _ , err = container .MappedPort (ctx , "21" )
83- require .NoError (t , err , "Failed to get mapped port info for container port 21/tcp (even though fixed)" )
83+ if _ , err = container .MappedPort (ctx , "21" ); err != nil {
84+ _ = container .Terminate (ctx )
85+ return nil , fmt .Errorf ("failed to get mapped port: %w" , err )
86+ }
8487
8588 // construct the Port struct based on our fixed request
8689 fixedHostNatPort , err := nat .NewPort ("tcp" , fixedHostControlPort )
87- require . NoError ( t , err , "Failed to create nat.Port for fixed host port" )
88-
89- t . Logf ( "FTP container should be accessible at: %s:%s (Control Plane) " , host , fixedHostControlPort )
90- t . Logf ( "FTP server using default config, passive ports %s mapped to host %s" , pasvPortRangeContainer , pasvPortRangeHost )
90+ if err != nil {
91+ _ = container . Terminate ( ctx )
92+ return nil , fmt . Errorf ( "failed to create nat.Port for fixed host port: %w " , err )
93+ }
9194
9295 time .Sleep (1 * time .Second )
9396
@@ -97,20 +100,18 @@ func NewFTPTestContainer(ctx context.Context, t *testing.T) *FTPTestContainer {
97100 Port : fixedHostNatPort , // use the manually constructed nat.Port for the fixed host port
98101 User : defaultUser ,
99102 Password : defaultPassword ,
100- }
103+ }, nil
101104}
102105
103- // connect function (Use default EPSV enabled)
106+ // connect establishes an FTP connection and logs in
104107func (fc * FTPTestContainer ) connect (ctx context.Context ) (* ftp.ServerConn , error ) {
105108 opts := []ftp.DialOption {
106109 ftp .DialWithTimeout (30 * time .Second ),
107110 ftp .DialWithContext (ctx ),
108- ftp .DialWithDebugOutput (os .Stdout ), // keep for debugging
109- // *** Use default (EPSV enabled) ***
110- // ftp.DialWithDisabledEPSV(true),
111+ ftp .DialWithDebugOutput (os .Stdout ),
111112 }
112113
113- connStr := fc .ConnectionString () // will use the fixed host port (e.g., 2121)
114+ connStr := fc .ConnectionString ()
114115 fmt .Printf ("Attempting FTP connection to: %s (User: %s)\n " , connStr , fc .User )
115116
116117 c , err := ftp .Dial (connStr , opts ... )
@@ -123,9 +124,7 @@ func (fc *FTPTestContainer) connect(ctx context.Context) (*ftp.ServerConn, error
123124 fmt .Printf ("Attempting FTP login with user: %s\n " , fc .User )
124125 if err := c .Login (fc .User , fc .Password ); err != nil {
125126 fmt .Printf ("FTP Login Error for user %s: %v\n " , fc .User , err )
126- if quitErr := c .Quit (); quitErr != nil {
127- fmt .Printf ("Warning: error closing FTP connection: %v\n " , quitErr )
128- }
127+ _ = c .Quit ()
129128 return nil , fmt .Errorf ("failed to login to FTP server with user %s: %w" , fc .User , err )
130129 }
131130 fmt .Printf ("FTP Login successful for user %s\n " , fc .User )
@@ -377,33 +376,3 @@ func splitPath(path string) []string {
377376 }
378377 return strings .Split (cleanPath , "/" )
379378}
380-
381- // logContainerError handles container startup errors with detailed logging
382- func (fc * FTPTestContainer ) logContainerError (_ context.Context , t * testing.T , container testcontainers.Container , err error , imageName string ) {
383- logCtx , logCancel := context .WithTimeout (context .Background (), 10 * time .Second )
384- defer logCancel ()
385-
386- fc .logContainerLogs (logCtx , t , container )
387- require .NoError (t , err , "Failed to create or start FTP container %s" , imageName )
388- }
389-
390- // logContainerLogs attempts to fetch and log container logs
391- func (fc * FTPTestContainer ) logContainerLogs (ctx context.Context , t * testing.T , container testcontainers.Container ) {
392- if container == nil {
393- t .Logf ("Container object was nil after GenericContainer failure." )
394- return
395- }
396-
397- logs , logErr := container .Logs (ctx )
398- if logErr != nil {
399- t .Logf ("Could not retrieve container logs after startup failure: %v" , logErr )
400- return
401- }
402-
403- logBytes , _ := io .ReadAll (logs )
404- if closeErr := logs .Close (); closeErr != nil {
405- t .Logf ("warning: failed to close logs reader: %v" , closeErr )
406- }
407-
408- t .Logf ("Container logs on startup failure:\n %s" , string (logBytes ))
409- }
0 commit comments