Skip to content

Commit 5edeb1e

Browse files
Merge pull request #28 from SwiftPackageIndex/fix-swift-metrics-unzip-2
Fix swift metrics unzip
2 parents a9e4d1f + 2d6e2a2 commit 5edeb1e

File tree

5 files changed

+113
-45
lines changed

5 files changed

+113
-45
lines changed

Sources/DocUploadBundle/DocUploadBundle.swift

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,12 +105,19 @@ public struct DocUploadBundle {
105105
)
106106
}
107107

108-
public func zip(to workDir: String) throws -> String {
108+
public func zip(to workDir: String, method: Zipper.Method = .library) throws -> String {
109109
let archiveURL = URL(fileURLWithPath: "\(workDir)/\(archiveName)")
110110
let metadataURL = URL(fileURLWithPath: "\(workDir)/metadata.json")
111111
try JSONEncoder().encode(metadata).write(to: metadataURL)
112112

113-
try Zipper.zip(paths: [metadataURL, URL(fileURLWithPath: sourcePath)], to: archiveURL)
113+
switch method {
114+
case .library, .zipTool(workingDirectory: .some(_)):
115+
try Zipper.zip(paths: [metadataURL, URL(fileURLWithPath: sourcePath)], to: archiveURL, method: method)
116+
117+
case .zipTool(.none):
118+
// By default, run the zip tool in the working directory
119+
try Zipper.zip(paths: [metadataURL, URL(fileURLWithPath: sourcePath)], to: archiveURL, method: .zipTool(workingDirectory: workDir))
120+
}
114121

115122
return archiveURL.path
116123
}

Sources/DocUploadBundle/Zipper.swift

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,51 @@ import Foundation
1717
import Zip
1818

1919

20-
enum Zipper {
21-
static func zip(paths inputPaths: [URL], to outputPath: URL) throws {
22-
try Zip.zipFiles(paths: inputPaths, zipFilePath: outputPath, password: nil, progress: nil)
23-
}
20+
public enum Zipper {
21+
public static func zip(paths inputPaths: [URL], to outputPath: URL, method: Method = .library) throws {
22+
switch method {
23+
case .library:
24+
do { try Zip.zipFiles(paths: inputPaths, zipFilePath: outputPath, password: nil, progress: nil) }
25+
catch ZipError.fileNotFound { throw Error.fileNotFound }
26+
catch ZipError.unzipFail { throw Error.unzipFail }
27+
catch ZipError.zipFail { throw Error.zipFail }
28+
catch { throw Error.generic(reason: "\(error)") }
2429

25-
static func unzip(from inputPath: URL, to outputPath: URL, fileOutputHandler: ((_ unzippedFile: URL) -> Void)? = nil) throws {
26-
do {
27-
try Zip.unzipFile(inputPath, destination: outputPath, overwrite: true, password: nil, fileOutputHandler: fileOutputHandler)
28-
} catch ZipError.unzipFail {
29-
// Try OS level unzip as a fallback
30-
// See https://github.com/SwiftPackageIndex/SwiftPackageIndex-Server/issues/3069
31-
let unzip = URL(fileURLWithPath: "/usr/bin/unzip")
32-
let process = try Process.run(unzip, arguments: ["-q", inputPath.path, "-d", outputPath.path])
33-
process.waitUntilExit()
30+
case let .zipTool(cwd):
31+
do {
32+
let process = Process()
33+
process.executableURL = zip
34+
process.arguments = ["-q", "-r", outputPath.path] + inputPaths.map(\.lastPathComponent)
35+
process.currentDirectoryURL = cwd.map(URL.init(fileURLWithPath:))
36+
try process.run()
37+
process.waitUntilExit()
38+
} catch {
39+
throw Error.generic(reason: "\(error)")
40+
}
3441
}
3542
}
43+
44+
public static func unzip(from inputPath: URL, to outputPath: URL, fileOutputHandler: ((_ unzippedFile: URL) -> Void)? = nil) throws {
45+
do { try Zip.unzipFile(inputPath, destination: outputPath, overwrite: true, password: nil, fileOutputHandler: fileOutputHandler) }
46+
catch ZipError.fileNotFound { throw Error.fileNotFound }
47+
catch ZipError.unzipFail { throw Error.unzipFail }
48+
catch ZipError.zipFail { throw Error.zipFail }
49+
catch { throw Error.generic(reason: "\(error)") }
50+
}
51+
52+
static let zip = URL(fileURLWithPath: "/usr/bin/zip")
53+
54+
public enum Method {
55+
case library
56+
case zipTool(workingDirectory: String? = nil)
57+
}
58+
59+
public enum Error: Swift.Error {
60+
case generic(reason: String)
61+
case fileNotFound
62+
case unzipFail
63+
case zipFail
64+
}
3665
}
3766

3867

Tests/DocUploadBundleTests/DocUploadBundleTests.swift

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -100,34 +100,4 @@ final class DocUploadBundleTests: XCTestCase {
100100
.init(bucket: "spi-prod-docs", path: "owner/name/feature-2.0.0"))
101101
}
102102

103-
func test_issue_3069() async throws {
104-
// https://github.com/SwiftPackageIndex/SwiftPackageIndex-Server/issues/3069
105-
try await withTempDir { tempDir in
106-
let url = fixtureUrl(for: "prod-apple-swift-metrics-main-e6a00d36.zip")
107-
XCTAssertNoThrow(
108-
try DocUploadBundle.unzip(bundle: url.path, outputPath: tempDir)
109-
)
110-
for pathComponent in ["metadata.json",
111-
"main/index.html",
112-
"main/index/index.json"] {
113-
let path = tempDir + "/" + pathComponent
114-
XCTAssertTrue(FileManager.default.fileExists(atPath: path), "does not exist: \(path)")
115-
}
116-
// test roundtrip, to ensure the zip library can zip/unzip its own product
117-
// zip
118-
let urls = [tempDir + "/metadata.json",
119-
tempDir + "/main"].map(URL.init(fileURLWithPath:))
120-
let zipped = URL(fileURLWithPath: tempDir + "/out.zip")
121-
try Zipper.zip(paths: urls, to: zipped)
122-
XCTAssertTrue(FileManager.default.fileExists(atPath: zipped.path))
123-
// unzip
124-
let out = URL(fileURLWithPath: tempDir + "/out")
125-
try Zipper.unzip(from: zipped, to: out)
126-
XCTAssertTrue(FileManager.default.fileExists(atPath: out.path))
127-
XCTAssertTrue(FileManager.default.fileExists(atPath: out.appendingPathComponent("metadata.json").path))
128-
XCTAssertTrue(FileManager.default.fileExists(atPath: out.appendingPathComponent("main/index.html").path))
129-
XCTAssertTrue(FileManager.default.fileExists(atPath: out.appendingPathComponent("main/index/index.json").path))
130-
}
131-
}
132-
133103
}
Binary file not shown.

Tests/DocUploadBundleTests/ZipTests.swift

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,14 @@ final class ZipTests: XCTestCase {
5757
let fileB = subdir.appendingPathComponent("b.txt")
5858
try "b".write(to: fileB, atomically: true, encoding: .utf8)
5959

60+
// temp/subdir/subsubdir
61+
let subsubdir = subdir.appendingPathComponent("subsubdir")
62+
try FileManager.default.createDirectory(at: subsubdir, withIntermediateDirectories: false)
63+
64+
// temp/subdir/subdir/c.txt
65+
let fileC = subsubdir.appendingPathComponent("c.txt")
66+
try "c".write(to: fileC, atomically: true, encoding: .utf8)
67+
6068
let zipFile = tempURL.appendingPathComponent("out.zip")
6169
try Zipper.zip(paths: [fileA, subdir], to: zipFile)
6270
XCTAssert(FileManager.default.fileExists(atPath: zipFile.path))
@@ -69,10 +77,64 @@ final class ZipTests: XCTestCase {
6977
// roundtrip/subdir/b.txt
7078
let fileA = roundtrip.appendingPathComponent("a.txt")
7179
let fileB = roundtrip.appendingPathComponent("subdir").appendingPathComponent("b.txt")
80+
let fileC = roundtrip.appendingPathComponent("subdir").appendingPathComponent("subsubdir").appendingPathComponent("c.txt")
81+
XCTAssert(FileManager.default.fileExists(atPath: fileA.path))
82+
XCTAssert(FileManager.default.fileExists(atPath: fileB.path))
83+
XCTAssert(FileManager.default.fileExists(atPath: fileC.path))
84+
XCTAssertEqual(try String(contentsOf: fileA), "a")
85+
XCTAssertEqual(try String(contentsOf: fileB), "b")
86+
XCTAssertEqual(try String(contentsOf: fileC), "c")
87+
}
88+
}
89+
}
90+
91+
func test_zip_roundtrip_shellTool() async throws {
92+
try XCTSkipIf(!FileManager.default.fileExists(atPath: Zipper.zip.path))
93+
94+
// Test basic zip roundtrip with the shellTool method
95+
try await withTempDir { tempDir in
96+
// temp
97+
let tempURL = URL(fileURLWithPath: tempDir)
98+
99+
// temp/a.txt
100+
let fileA = tempURL.appendingPathComponent("a.txt")
101+
try "a".write(to: fileA, atomically: true, encoding: .utf8)
102+
103+
// temp/subdir/
104+
let subdir = tempURL.appendingPathComponent("subdir")
105+
try FileManager.default.createDirectory(at: subdir, withIntermediateDirectories: false)
106+
107+
// temp/subdir/b.txt
108+
let fileB = subdir.appendingPathComponent("b.txt")
109+
try "b".write(to: fileB, atomically: true, encoding: .utf8)
110+
111+
// temp/subdir/subsubdir
112+
let subsubdir = subdir.appendingPathComponent("subsubdir")
113+
try FileManager.default.createDirectory(at: subsubdir, withIntermediateDirectories: false)
114+
115+
// temp/subdir/subdir/c.txt
116+
let fileC = subsubdir.appendingPathComponent("c.txt")
117+
try "c".write(to: fileC, atomically: true, encoding: .utf8)
118+
119+
let zipFile = tempURL.appendingPathComponent("out.zip")
120+
try Zipper.zip(paths: [fileA, subdir], to: zipFile, method: .zipTool(workingDirectory: tempDir))
121+
XCTAssert(FileManager.default.fileExists(atPath: zipFile.path))
122+
123+
do { // unzip what we zipped and check results
124+
let roundtrip = tempURL.appendingPathComponent("roundtrip")
125+
try Zipper.unzip(from: zipFile, to: roundtrip)
126+
XCTAssert(FileManager.default.fileExists(atPath: roundtrip.path))
127+
// roundtrip/a.txt
128+
// roundtrip/subdir/b.txt
129+
let fileA = roundtrip.appendingPathComponent("a.txt")
130+
let fileB = roundtrip.appendingPathComponent("subdir").appendingPathComponent("b.txt")
131+
let fileC = roundtrip.appendingPathComponent("subdir").appendingPathComponent("subsubdir").appendingPathComponent("c.txt")
72132
XCTAssert(FileManager.default.fileExists(atPath: fileA.path))
73133
XCTAssert(FileManager.default.fileExists(atPath: fileB.path))
134+
XCTAssert(FileManager.default.fileExists(atPath: fileC.path))
74135
XCTAssertEqual(try String(contentsOf: fileA), "a")
75136
XCTAssertEqual(try String(contentsOf: fileB), "b")
137+
XCTAssertEqual(try String(contentsOf: fileC), "c")
76138
}
77139
}
78140
}

0 commit comments

Comments
 (0)