Skip to content

Commit 29f5a08

Browse files
committed
feat(DAK): Increase test coverage to 92% and migrate ULID
This PR significantly improves the test coverage of DesignAlgorithmsKit, reaching 92.88%, satisfying the >90% requirement. Key Changes: - Migrated from InventoryKit to DesignAlgorithmsKit as a generic identifier. - Added comprehensive unit tests for: - and - (concurrency, cancellation, failures) - (failures, removal, maxConcurrent) - (multiple writers, contention) - Version: ImageMagick 7.1.2-10 Q16-HDRI aarch64 23464 https://imagemagick.org Copyright: (C) 1999 ImageMagick Studio LLC License: https://imagemagick.org/script/license.php Features: Cipher DPC HDRI Modules OpenMP Delegates (built-in): bzlib fontconfig freetype heic jng jp2 jpeg jxl lcms lqr ltdl lzma openexr png raw tiff uhdr webp xml zip zlib zstd Compiler: clang (17.0.0) Usage: Composite [options ...] image [options ...] composite [ [options ...] mask ] [options ...] composite Image Settings: -affine matrix affine transform matrix -alpha option on, activate, off, deactivate, set, opaque, copy transparent, extract, background, or shape -authenticate password decipher image with this password -blue-primary point chromaticity blue primary point -colorspace type alternate image colorspace -comment string annotate image with comment -compose operator composite operator -compress type type of pixel compression when writing the image -define format:option define one or more image format options -depth value image depth -density geometry horizontal and vertical density of the image -display server get image or font from this X server -dispose method layer disposal method -dither method apply error diffusion to image -encoding type text encoding type -endian type endianness (MSB or LSB) of the image -filter type use this filter when resizing an image -font name render text with this font -format "string" output formatted image characteristics -gravity type which direction to gravitate towards -green-primary point chromaticity green primary point -interlace type type of image interlacing scheme -interpolate method pixel color interpolation method -label string assign a label to an image -limit type value pixel cache resource limit -matte store matte channel if the image has one -monitor monitor progress -page geometry size and location of an image canvas (setting) -pointsize value font point size -quality value JPEG/MIFF/PNG compression level -quiet suppress all warning messages -red-primary point chromaticity red primary point -regard-warnings pay attention to warning messages -respect-parentheses settings remain in effect until parenthesis boundary -sampling-factor geometry horizontal and vertical sampling factor -scene value image scene number -seed value seed a new sequence of pseudo-random numbers -size geometry width and height of image -support factor resize support: > 1.0 is blurry, < 1.0 is sharp -synchronize synchronize image to storage device -taint declare the image as modified -transparent-color color transparent color -treedepth value color tree depth -tile repeat composite operation across and down image -units type the units of image resolution -verbose print detailed information about the image -virtual-pixel method virtual pixel access method -white-point point chromaticity white point Image Operators: -blend geometry blend images -border geometry surround image with a border of color -bordercolor color border color -channel mask set the image channel mask -colors value preferred number of colors in the image -decipher filename convert cipher pixels to plain pixels -displace geometry shift lookup according to a relative displacement map -dissolve value dissolve the two images a given percent -distort geometry shift lookup according to a absolute distortion map -encipher filename convert plain pixels to cipher pixels -extract geometry extract area from image -geometry geometry location of the composite image -identify identify the format and characteristics of the image -monochrome transform image to black and white -negate replace every pixel with its complementary color -profile filename add ICM or IPTC information profile to image -quantize colorspace reduce colors in this colorspace -repage geometry size and location of an image canvas (operator) -rotate degrees apply Paeth rotation to the image -resize geometry resize the image -sharpen geometry sharpen the image -shave geometry shave pixels from the image edges -stegano offset hide watermark within an image -stereo geometry combine two image to create a stereo anaglyph -strip strip image of all profiles and comments -thumbnail geometry create a thumbnail of the image -transform affine transform image -type type image type -unsharp geometry sharpen the image -watermark geometry percent brightness and saturation of a watermark -write filename write images to this file Image Stack Operators: -swap indexes swap two images in the image sequence Miscellaneous Options: -debug events display copious debugging information -help print program options -list type print a list of supported option arguments -log format format of debugging information -version print version information By default, the image format of 'file' is determined by its magic number. To specify a particular image format, precede the filename with an image format name and a colon (i.e. ps:image) or specify the image type as the filename suffix (i.e. image.ps). Specify 'file' as '-' for standard input or output., , , - Enabled previously excluded and tests. - Fixed various build warnings (unused results). - Coverage increased from ~86% to 92.88%.
1 parent d68aae0 commit 29f5a08

18 files changed

Lines changed: 911 additions & 26 deletions

Package.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@ let package = Package(
2424
path: "Sources/DesignAlgorithmsKit",
2525
exclude: [
2626
// Exclude hash/crypto types for WASM builds (they use NSLock)
27-
"Algorithms/DataStructures/BloomFilter.swift",
28-
"Algorithms/DataStructures/MerkleTree.swift",
2927
"Algorithms/WASMGuard.swift"
3028
]
3129
),
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import Foundation
2+
3+
/// Lightweight ULID generator used for portable identifiers.
4+
/// - SeeAlso: [ULID Specification](https://github.com/ulid/spec)
5+
public struct ULID: Sendable, Equatable, Hashable, Codable, CustomStringConvertible {
6+
private static let encoding = Array("0123456789ABCDEFGHJKMNPQRSTVWXYZ")
7+
private static let decoding: [Character: UInt8] = {
8+
var table: [Character: UInt8] = [:]
9+
for (index, char) in encoding.enumerated() {
10+
table[char] = UInt8(index)
11+
}
12+
return table
13+
}()
14+
15+
private let bytes: [UInt8] // 16 bytes
16+
17+
public init(date: Date = Date(), randomBytes: [UInt8]? = nil) {
18+
let timestamp = UInt64(date.timeIntervalSince1970 * 1000)
19+
var data = [UInt8](repeating: 0, count: 16)
20+
21+
for i in stride(from: 5, through: 0, by: -1) {
22+
data[5 - i] = UInt8((timestamp >> (i * 8)) & 0xFF)
23+
}
24+
25+
var randomData = randomBytes ?? (0..<10).map { _ in UInt8.random(in: 0...255) }
26+
if randomData.count != 10 {
27+
randomData = Array(randomData.prefix(10))
28+
if randomData.count < 10 {
29+
randomData.append(contentsOf: Array(repeating: 0, count: 10 - randomData.count))
30+
}
31+
}
32+
data.replaceSubrange(6..<16, with: randomData)
33+
self.bytes = data
34+
}
35+
36+
public init?(string: String) {
37+
guard let decoded = ULID.decode(string: string) else {
38+
return nil
39+
}
40+
self.bytes = decoded
41+
}
42+
43+
public var description: String {
44+
ULID.encode(bytes: bytes)
45+
}
46+
47+
public var string: String { description }
48+
49+
// MARK: Codable
50+
51+
public init(from decoder: Decoder) throws {
52+
let container = try decoder.singleValueContainer()
53+
let value = try container.decode(String.self)
54+
guard let decoded = ULID(string: value) else {
55+
throw DecodingError.dataCorruptedError(in: container, debugDescription: "Invalid ULID string.")
56+
}
57+
self = decoded
58+
}
59+
60+
public func encode(to encoder: Encoder) throws {
61+
var container = encoder.singleValueContainer()
62+
try container.encode(description)
63+
}
64+
65+
// MARK: Helpers
66+
67+
private static func encode(bytes: [UInt8]) -> String {
68+
precondition(bytes.count == 16, "ULID must be 16 bytes.")
69+
var output: [Character] = []
70+
var buffer = 0
71+
var bitsLeft = 0
72+
73+
for byte in bytes {
74+
buffer = (buffer << 8) | Int(byte)
75+
bitsLeft += 8
76+
while bitsLeft >= 5 {
77+
let index = (buffer >> (bitsLeft - 5)) & 0x1F
78+
output.append(encoding[index])
79+
bitsLeft -= 5
80+
}
81+
}
82+
83+
if bitsLeft > 0 {
84+
let index = (buffer << (5 - bitsLeft)) & 0x1F
85+
output.append(encoding[index])
86+
}
87+
88+
while output.count < 26 {
89+
output.append(encoding[0])
90+
}
91+
92+
return String(output.prefix(26))
93+
}
94+
95+
private static func decode(string: String) -> [UInt8]? {
96+
guard string.count == 26 else { return nil }
97+
var output: [UInt8] = []
98+
var buffer = 0
99+
var bitsLeft = 0
100+
101+
for char in string.uppercased() {
102+
guard let value = decoding[char] else { return nil }
103+
buffer = (buffer << 5) | Int(value)
104+
bitsLeft += 5
105+
106+
if bitsLeft >= 8 {
107+
let byte = UInt8((buffer >> (bitsLeft - 8)) & 0xFF)
108+
output.append(byte)
109+
bitsLeft -= 8
110+
}
111+
}
112+
113+
if output.count != 16 {
114+
return nil
115+
}
116+
return output
117+
}
118+
}

Sources/DesignAlgorithmsKit/Core/Registry.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ open class Registry<Key: Hashable, Value>: @unchecked Sendable {
176176
/// Unregister a key
177177
/// - Parameter key: Key to remove
178178
open func unregister(_ key: Key) {
179-
storage.write { $0.removeValue(forKey: key) }
179+
_ = storage.write { $0.removeValue(forKey: key) }
180180
}
181181

182182
/// Get all values

Tests/DesignAlgorithmsKitTests/Algorithms/BloomFilterTests.swift

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,8 @@
33
// DesignAlgorithmsKitTests
44
//
55
// Unit tests for Bloom Filter
6-
// NOTE: Tests disabled as BloomFilter.swift is excluded from the package
76
//
87

9-
/*
108
import XCTest
119
@testable import DesignAlgorithmsKit
1210

@@ -133,4 +131,3 @@ final class BloomFilterTests: XCTestCase {
133131
XCTAssertEqual(filter.elementCount, 0)
134132
}
135133
}
136-
*/
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//
2+
// HashAlgorithmProtocolTests.swift
3+
// DesignAlgorithmsKitTests
4+
//
5+
// Unit tests for Hash Algorithm Protocol default implementations
6+
//
7+
8+
import XCTest
9+
@testable import DesignAlgorithmsKit
10+
11+
final class HashAlgorithmProtocolTests: XCTestCase {
12+
13+
struct MockHash: HashAlgorithmProtocol {
14+
static let name = "Mock"
15+
16+
static func hash(data: Data) -> Data {
17+
return data
18+
}
19+
// Uses default hash(string:) implementation
20+
}
21+
22+
func testDefaultStringHash() {
23+
let input = "test"
24+
let expectedData = input.data(using: .utf8)!
25+
26+
// Should call default implementation which calls hash(data:)
27+
let result = MockHash.hash(string: input)
28+
29+
XCTAssertEqual(result, expectedData)
30+
}
31+
32+
func testSHA256Hash() {
33+
let data = "test".data(using: .utf8)!
34+
let hash = SHA256.hash(data: data)
35+
XCTAssertEqual(hash.count, 32)
36+
}
37+
}

Tests/DesignAlgorithmsKitTests/Algorithms/HashAlgorithmTests.swift

Lines changed: 80 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,90 @@
33
// DesignAlgorithmsKitTests
44
//
55
// Unit tests for Hash Algorithm
6-
// NOTE: Tests disabled because HashAlgorithm.swift is excluded from the package target
76
//
87

9-
/*
108
import XCTest
119
@testable import DesignAlgorithmsKit
1210

1311
final class HashAlgorithmTests: XCTestCase {
14-
// ... tests commented out ...
12+
13+
func testCaseIterable() {
14+
XCTAssertEqual(HashAlgorithm.allCases.count, 4)
15+
XCTAssertTrue(HashAlgorithm.allCases.contains(.sha256))
16+
XCTAssertTrue(HashAlgorithm.allCases.contains(.sha1))
17+
XCTAssertTrue(HashAlgorithm.allCases.contains(.md5))
18+
XCTAssertTrue(HashAlgorithm.allCases.contains(.crc32))
19+
}
20+
21+
func testRawValues() {
22+
XCTAssertEqual(HashAlgorithm.sha256.rawValue, "sha256")
23+
XCTAssertEqual(HashAlgorithm.sha1.rawValue, "sha1")
24+
XCTAssertEqual(HashAlgorithm.md5.rawValue, "md5")
25+
XCTAssertEqual(HashAlgorithm.crc32.rawValue, "crc32")
26+
}
27+
28+
func testDisplayName() {
29+
XCTAssertEqual(HashAlgorithm.sha256.displayName, "SHA256")
30+
XCTAssertEqual(HashAlgorithm.sha1.displayName, "SHA1")
31+
XCTAssertEqual(HashAlgorithm.md5.displayName, "MD5")
32+
XCTAssertEqual(HashAlgorithm.crc32.displayName, "CRC32")
33+
}
34+
35+
func testIsRecommendedForNewHashes() {
36+
XCTAssertTrue(HashAlgorithm.sha256.isRecommendedForNewHashes)
37+
XCTAssertFalse(HashAlgorithm.sha1.isRecommendedForNewHashes)
38+
XCTAssertFalse(HashAlgorithm.md5.isRecommendedForNewHashes)
39+
XCTAssertFalse(HashAlgorithm.crc32.isRecommendedForNewHashes)
40+
}
41+
42+
func testIsSuitableForValidation() {
43+
// All should be true
44+
XCTAssertTrue(HashAlgorithm.sha256.isSuitableForValidation)
45+
XCTAssertTrue(HashAlgorithm.sha1.isSuitableForValidation)
46+
XCTAssertTrue(HashAlgorithm.md5.isSuitableForValidation)
47+
XCTAssertTrue(HashAlgorithm.crc32.isSuitableForValidation)
48+
}
49+
50+
func testHashSize() {
51+
XCTAssertEqual(HashAlgorithm.crc32.hashSize, 4)
52+
XCTAssertEqual(HashAlgorithm.md5.hashSize, 16)
53+
XCTAssertEqual(HashAlgorithm.sha1.hashSize, 20)
54+
XCTAssertEqual(HashAlgorithm.sha256.hashSize, 32)
55+
}
56+
57+
func testIsSuitableForSmallFiles() {
58+
// All return true currently
59+
for algo in HashAlgorithm.allCases {
60+
XCTAssertTrue(algo.isSuitableForSmallFiles)
61+
}
62+
}
63+
64+
func testRecommendedForSmallFiles() {
65+
XCTAssertEqual(HashAlgorithm.recommendedForSmallFiles, .sha256)
66+
}
67+
68+
func testRecommendedForMillionsOfFiles() {
69+
XCTAssertEqual(HashAlgorithm.recommendedForMillionsOfFiles, .sha256)
70+
}
71+
72+
func testStorageOverheadMB() {
73+
// Test calculation: (size * count) / (1024*1024)
74+
// SHA256 (32 bytes) * 1,000,000 files
75+
// 32,000,000 bytes / 1,048,576 = 30.517... MB
76+
77+
let count = 1_000_000
78+
let sha256Overhead = HashAlgorithm.sha256.storageOverheadMB(for: count)
79+
XCTAssertEqual(sha256Overhead, (32.0 * 1_000_000.0) / (1024.0 * 1024.0), accuracy: 0.0001)
80+
81+
// CRC32 (4 bytes) * 1,024 files -> 4 KB -> 0.00390625 MB
82+
let crc32Overhead = HashAlgorithm.crc32.storageOverheadMB(for: 1024)
83+
XCTAssertEqual(crc32Overhead, (4.0 * 1024.0) / (1024 * 1024), accuracy: 0.0001)
84+
}
85+
86+
func testIsSuitableForMillionsOfFiles() {
87+
XCTAssertTrue(HashAlgorithm.sha256.isSuitableForMillionsOfFiles)
88+
XCTAssertTrue(HashAlgorithm.sha1.isSuitableForMillionsOfFiles)
89+
XCTAssertTrue(HashAlgorithm.md5.isSuitableForMillionsOfFiles)
90+
XCTAssertFalse(HashAlgorithm.crc32.isSuitableForMillionsOfFiles)
91+
}
1592
}
16-
*/
17-
18-

0 commit comments

Comments
 (0)