@@ -1843,6 +1843,156 @@ extension IntegrationSuite {
18431843 }
18441844 }
18451845
1846+ func testCopyInFileToExistingDirectory( ) async throws {
1847+ let id = " test-copy-in-file-to-dir "
1848+
1849+ let bs = try await bootstrap ( id)
1850+
1851+ let testContent = " copy into an existing guest directory "
1852+ let hostFile = FileManager . default. uniqueTemporaryDirectory ( create: true )
1853+ . appendingPathComponent ( " host-file.txt " )
1854+ try testContent. write ( to: hostFile, atomically: true , encoding: . utf8)
1855+
1856+ let container = try LinuxContainer ( id, rootfs: bs. rootfs, vmm: bs. vmm) { config in
1857+ config. process. arguments = [ " sleep " , " 100 " ]
1858+ config. bootLog = bs. bootLog
1859+ }
1860+
1861+ do {
1862+ try await container. create ( )
1863+ try await container. start ( )
1864+
1865+ let mkdir = try await container. exec ( " create-copy-target " ) { config in
1866+ config. arguments = [ " mkdir " , " -p " , " /tmp/copy-target " ]
1867+ }
1868+ try await mkdir. start ( )
1869+ let mkdirStatus = try await mkdir. wait ( )
1870+ try await mkdir. delete ( )
1871+
1872+ guard mkdirStatus. exitCode == 0 else {
1873+ throw IntegrationError . assert ( msg: " mkdir failed with status \( mkdirStatus) " )
1874+ }
1875+
1876+ try await container. copyIn (
1877+ from: hostFile,
1878+ to: URL ( filePath: " /tmp/copy-target " )
1879+ )
1880+
1881+ let buffer = BufferWriter ( )
1882+ let verify = try await container. exec ( " verify-copy-target " ) { config in
1883+ config. arguments = [ " cat " , " /tmp/copy-target/host-file.txt " ]
1884+ config. stdout = buffer
1885+ }
1886+ try await verify. start ( )
1887+ let verifyStatus = try await verify. wait ( )
1888+ try await verify. delete ( )
1889+
1890+ guard verifyStatus. exitCode == 0 else {
1891+ throw IntegrationError . assert ( msg: " cat copied file failed with status \( verifyStatus) " )
1892+ }
1893+ guard String ( data: buffer. data, encoding: . utf8) == testContent else {
1894+ throw IntegrationError . assert ( msg: " copied file should land under the existing destination directory " )
1895+ }
1896+
1897+ try await container. kill ( . kill)
1898+ try await container. wait ( )
1899+ try await container. stop ( )
1900+ } catch {
1901+ try ? await container. stop ( )
1902+ throw error
1903+ }
1904+ }
1905+
1906+ func testCopyInFileToMissingDirectoryFails( ) async throws {
1907+ let id = " test-copy-in-file-missing-dir "
1908+
1909+ let bs = try await bootstrap ( id)
1910+
1911+ let hostFile = FileManager . default. uniqueTemporaryDirectory ( create: true )
1912+ . appendingPathComponent ( " host-file.txt " )
1913+ try " missing destination directory " . write ( to: hostFile, atomically: true , encoding: . utf8)
1914+
1915+ let container = try LinuxContainer ( id, rootfs: bs. rootfs, vmm: bs. vmm) { config in
1916+ config. process. arguments = [ " sleep " , " 100 " ]
1917+ config. bootLog = bs. bootLog
1918+ }
1919+
1920+ do {
1921+ try await container. create ( )
1922+ try await container. start ( )
1923+
1924+ do {
1925+ try await container. copyIn (
1926+ from: hostFile,
1927+ to: URL ( filePath: " /tmp/missing-copy-target/ " )
1928+ )
1929+ throw IntegrationError . assert ( msg: " copyIn should fail when copying a file to a missing destination directory " )
1930+ } catch let error as ContainerizationError where error. code == . invalidArgument {
1931+ guard error. description. contains ( " destination directory does not exist " ) else {
1932+ throw IntegrationError . assert ( msg: " unexpected copyIn error: \( error) " )
1933+ }
1934+ }
1935+
1936+ try await container. kill ( . kill)
1937+ try await container. wait ( )
1938+ try await container. stop ( )
1939+ } catch {
1940+ try ? await container. stop ( )
1941+ throw error
1942+ }
1943+ }
1944+
1945+ func testCopyInDirectoryOverExistingFileFails( ) async throws {
1946+ let id = " test-copy-in-dir-over-file "
1947+
1948+ let bs = try await bootstrap ( id)
1949+
1950+ let hostDir = FileManager . default. uniqueTemporaryDirectory ( create: true )
1951+ . appendingPathComponent ( " host-dir " )
1952+ try FileManager . default. createDirectory ( at: hostDir, withIntermediateDirectories: true )
1953+ try " directory content " . write ( to: hostDir. appendingPathComponent ( " file.txt " ) , atomically: true , encoding: . utf8)
1954+
1955+ let container = try LinuxContainer ( id, rootfs: bs. rootfs, vmm: bs. vmm) { config in
1956+ config. process. arguments = [ " sleep " , " 100 " ]
1957+ config. bootLog = bs. bootLog
1958+ }
1959+
1960+ do {
1961+ try await container. create ( )
1962+ try await container. start ( )
1963+
1964+ let createFile = try await container. exec ( " create-existing-file " ) { config in
1965+ config. arguments = [ " sh " , " -c " , " echo -n existing > /tmp/existing-file " ]
1966+ }
1967+ try await createFile. start ( )
1968+ let createStatus = try await createFile. wait ( )
1969+ try await createFile. delete ( )
1970+
1971+ guard createStatus. exitCode == 0 else {
1972+ throw IntegrationError . assert ( msg: " failed to create existing file, status \( createStatus) " )
1973+ }
1974+
1975+ do {
1976+ try await container. copyIn (
1977+ from: hostDir,
1978+ to: URL ( filePath: " /tmp/existing-file " )
1979+ )
1980+ throw IntegrationError . assert ( msg: " copyIn should fail when copying a directory over an existing file " )
1981+ } catch let error as ContainerizationError where error. code == . invalidArgument {
1982+ guard error. description. contains ( " cannot copy directory over existing file " ) else {
1983+ throw IntegrationError . assert ( msg: " unexpected copyIn error: \( error) " )
1984+ }
1985+ }
1986+
1987+ try await container. kill ( . kill)
1988+ try await container. wait ( )
1989+ try await container. stop ( )
1990+ } catch {
1991+ try ? await container. stop ( )
1992+ throw error
1993+ }
1994+ }
1995+
18461996 func testCopyOut( ) async throws {
18471997 let id = " test-copy-out "
18481998
0 commit comments