@@ -525,6 +525,232 @@ extension TestCLIBuildBase {
525525 #expect( try self . inspectImage ( imageName) == imageName, " expected to have successfully built \( imageName) " )
526526 }
527527
528+ @Test func testLowercaseDockerfile( ) throws {
529+ // Test 1: COPY with uppercase
530+ let tempDir1 : URL = try createTempDir ( )
531+ let dockerfile1 =
532+ """
533+ FROM ghcr.io/linuxcontainers/alpine:3.20
534+ COPY . /app
535+ RUN test -f /app/testfile.txt
536+ """
537+ let context1 : [ FileSystemEntry ] = [
538+ . file( " testfile.txt " , content: . data( " test " . data ( using: . utf8) !) )
539+ ]
540+ try createContext ( tempDir: tempDir1, dockerfile: dockerfile1, context: context1)
541+ let imageName1 = " registry.local/copy-uppercase: \( UUID ( ) . uuidString) "
542+ try self . build ( tag: imageName1, tempDir: tempDir1)
543+ #expect( try self . inspectImage ( imageName1) == imageName1, " expected COPY to work " )
544+
545+ // Test 2: copy with lowercase
546+ let tempDir2 : URL = try createTempDir ( )
547+ let dockerfile2 =
548+ """
549+ FROM ghcr.io/linuxcontainers/alpine:3.20
550+ copy . /app
551+ RUN test -f /app/testfile.txt
552+ """
553+ let context2 : [ FileSystemEntry ] = [
554+ . file( " testfile.txt " , content: . data( " test " . data ( using: . utf8) !) )
555+ ]
556+ try createContext ( tempDir: tempDir2, dockerfile: dockerfile2, context: context2)
557+ let imageName2 = " registry.local/copy-lowercase: \( UUID ( ) . uuidString) "
558+ try self . build ( tag: imageName2, tempDir: tempDir2)
559+ #expect( try self . inspectImage ( imageName2) == imageName2, " expected copy to work " )
560+
561+ // Test 3: ADD with uppercase
562+ let tempDir3 : URL = try createTempDir ( )
563+ let dockerfile3 =
564+ """
565+ FROM ghcr.io/linuxcontainers/alpine:3.20
566+ ADD . /app
567+ RUN test -f /app/testfile.txt
568+ """
569+ let context3 : [ FileSystemEntry ] = [
570+ . file( " testfile.txt " , content: . data( " test " . data ( using: . utf8) !) )
571+ ]
572+ try createContext ( tempDir: tempDir3, dockerfile: dockerfile3, context: context3)
573+ let imageName3 = " registry.local/add-uppercase: \( UUID ( ) . uuidString) "
574+ try self . build ( tag: imageName3, tempDir: tempDir3)
575+ #expect( try self . inspectImage ( imageName3) == imageName3, " expected ADD to work " )
576+
577+ // Test 4: add with lowercase
578+ let tempDir4 : URL = try createTempDir ( )
579+ let dockerfile4 =
580+ """
581+ FROM ghcr.io/linuxcontainers/alpine:3.20
582+ add . /app
583+ RUN test -f /app/testfile.txt
584+ """
585+ let context4 : [ FileSystemEntry ] = [
586+ . file( " testfile.txt " , content: . data( " test " . data ( using: . utf8) !) )
587+ ]
588+ try createContext ( tempDir: tempDir4, dockerfile: dockerfile4, context: context4)
589+ let imageName4 = " registry.local/add-lowercase: \( UUID ( ) . uuidString) "
590+ try self . build ( tag: imageName4, tempDir: tempDir4)
591+ #expect( try self . inspectImage ( imageName4) == imageName4, " expected add to work " )
592+ }
593+
594+ @Test func testRunWithBindMount( ) throws {
595+ let tempDir : URL = try createTempDir ( )
596+ let dockerfile =
597+ """
598+ FROM ghcr.io/linuxcontainers/alpine:3.20
599+
600+ # Use bind mount to access build context during RUN
601+ RUN --mount=type=bind,source=.,target=/mnt/context \
602+ set -e; \
603+ echo " Checking files in bind mount... " ; \
604+ ls -la /mnt/context/; \
605+ \
606+ echo " Verifying files are accessible in mount... " ; \
607+ if [ ! -f /mnt/context/app.py ]; then \
608+ echo " ERROR: app.py should be in bind mount! " ; \
609+ exit 1; \
610+ fi; \
611+ if [ ! -f /mnt/context/config.yaml ]; then \
612+ echo " ERROR: config.yaml should be in bind mount! " ; \
613+ exit 1; \
614+ fi; \
615+ \
616+ echo " RUN --mount bind check passed! " ; \
617+ cp /mnt/context/app.py /app.py
618+
619+ RUN cat /app.py
620+ """
621+
622+ let context : [ FileSystemEntry ] = [
623+ . file( " app.py " , content: . data( " print('Hello from bind mount') " . data ( using: . utf8) !) ) ,
624+ . file( " config.yaml " , content: . data( " key: value " . data ( using: . utf8) !) ) ,
625+ ]
626+
627+ try createContext ( tempDir: tempDir, dockerfile: dockerfile, context: context)
628+ let imageName = " registry.local/bind-mount-test: \( UUID ( ) . uuidString) "
629+ try self . build ( tag: imageName, tempDir: tempDir)
630+ #expect( try self . inspectImage ( imageName) == imageName, " expected to have successfully built \( imageName) " )
631+ }
632+
633+ @Test func testBuildDockerIgnore( ) throws {
634+ let tempDir : URL = try createTempDir ( )
635+ let dockerfile =
636+ """
637+ FROM ghcr.io/linuxcontainers/alpine:3.20
638+
639+ # Copy all files - should respect .dockerignore
640+ COPY . /app
641+
642+ # Verify specific files are excluded
643+ RUN set -e; \
644+ echo " Checking specific file exclusion... " ; \
645+ if [ -f /app/secret.txt ]; then \
646+ echo " ERROR: secret.txt should be excluded! " ; \
647+ exit 1; \
648+ fi
649+
650+ # Verify wildcard *.log files are excluded
651+ RUN set -e; \
652+ echo " Checking *.log exclusion... " ; \
653+ if [ -f /app/debug.log ]; then \
654+ echo " ERROR: debug.log should be excluded by *.log pattern! " ; \
655+ exit 1; \
656+ fi; \
657+ if ls /app/logs/*.log 2>/dev/null; then \
658+ echo " ERROR: logs/*.log files should be excluded! " ; \
659+ exit 1; \
660+ fi
661+
662+ # Verify exception pattern (!important.log) works
663+ RUN set -e; \
664+ echo " Checking exception pattern... " ; \
665+ if [ ! -f /app/important.log ]; then \
666+ echo " ERROR: important.log should be included (exception with !) " ; \
667+ exit 1; \
668+ fi
669+
670+ # Verify *.tmp files are excluded
671+ RUN set -e; \
672+ echo " Checking *.tmp exclusion... " ; \
673+ if find /app -name " *.tmp " | grep .; then \
674+ echo " ERROR: .tmp files should be excluded! " ; \
675+ exit 1; \
676+ fi
677+
678+ # Verify directories are excluded
679+ RUN set -e; \
680+ echo " Checking directory exclusion... " ; \
681+ if [ -d /app/temp ]; then \
682+ echo " ERROR: temp/ directory should be excluded! " ; \
683+ exit 1; \
684+ fi; \
685+ if [ -d /app/node_modules ]; then \
686+ echo " ERROR: node_modules/ should be excluded! " ; \
687+ exit 1; \
688+ fi
689+
690+ # Verify included files ARE present
691+ RUN set -e; \
692+ echo " Checking included files... " ; \
693+ if [ ! -f /app/main.go ]; then \
694+ echo " ERROR: main.go should be included! " ; \
695+ exit 1; \
696+ fi; \
697+ if [ ! -f /app/README.md ]; then \
698+ echo " ERROR: README.md should be included! " ; \
699+ exit 1; \
700+ fi; \
701+ if [ ! -f /app/src/app.go ]; then \
702+ echo " ERROR: src/app.go should be included! " ; \
703+ exit 1; \
704+ fi; \
705+ echo " All .dockerignore checks passed! "
706+ """
707+
708+ let dockerignore =
709+ """
710+ # Exclude specific files
711+ secret.txt
712+
713+ # Exclude all log files
714+ *.log
715+ **/*.log
716+
717+ # But make an exception for important.log
718+ !important.log
719+
720+ # Exclude all temporary files
721+ *.tmp
722+ **/*.tmp
723+
724+ # Exclude directories
725+ temp/
726+ node_modules/
727+ """
728+
729+ let context : [ FileSystemEntry ] = [
730+ . file( " .dockerignore " , content: . data( dockerignore. data ( using: . utf8) !) ) ,
731+ . file( " secret.txt " , content: . data( " secret content " . data ( using: . utf8) !) ) ,
732+ . file( " debug.log " , content: . data( " debug log content " . data ( using: . utf8) !) ) ,
733+ . file( " important.log " , content: . data( " important log content " . data ( using: . utf8) !) ) ,
734+ . file( " cache.tmp " , content: . data( " cache " . data ( using: . utf8) !) ) ,
735+ . file( " main.go " , content: . data( " package main " . data ( using: . utf8) !) ) ,
736+ . file( " README.md " , content: . data( " # README " . data ( using: . utf8) !) ) ,
737+ . directory( " temp " ) ,
738+ . file( " temp/cache.tmp " , content: . data( " temp cache " . data ( using: . utf8) !) ) ,
739+ . directory( " logs " ) ,
740+ . file( " logs/app.log " , content: . data( " app log " . data ( using: . utf8) !) ) ,
741+ . directory( " node_modules " ) ,
742+ . file( " node_modules/package.json " , content: . data( " {} " . data ( using: . utf8) !) ) ,
743+ . directory( " src " ) ,
744+ . file( " src/app.go " , content: . data( " package src " . data ( using: . utf8) !) ) ,
745+ . file( " src/test.tmp " , content: . data( " temp " . data ( using: . utf8) !) ) ,
746+ ]
747+
748+ try createContext ( tempDir: tempDir, dockerfile: dockerfile, context: context)
749+ let imageName = " registry.local/dockerignore-test: \( UUID ( ) . uuidString) "
750+ try self . build ( tag: imageName, tempDir: tempDir)
751+ #expect( try self . inspectImage ( imageName) == imageName, " expected to have successfully built \( imageName) " )
752+ }
753+
528754 @Test func testBuildNoCachePullLatestImage( ) throws {
529755 let tempDir : URL = try createTempDir ( )
530756 let dockerfile =
0 commit comments