Skip to content

Commit 3d6d16b

Browse files
authored
Merge pull request #46 from stlab/npd/more-tests
Add tests for errors and algorithms
2 parents 153c7e8 + f6e4c73 commit 3d6d16b

4 files changed

Lines changed: 64 additions & 21 deletions

File tree

better-code/src/chapter-2-contracts.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1138,7 +1138,7 @@ contract is an engineering decision you will have to make. To reduce
11381138
the risk you could add this assertion[^checks], which will stop the program if
11391139
the ordering is strict-weak:
11401140

1141-
```swift
1141+
```swift,ignore
11421142
precondition(
11431143
self.isEmpty || areInOrder(first!, first!),
11441144
"Total preorder required; did you pass a strict-weak ordering?")

better-code/src/chapter-3-errors.md

Lines changed: 46 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ or misspelled. Guessing correctly affects not only the quality of the
6262
error message, but also whether further diagnostics will be
6363
useful. For example, in this code, the `while` keyword is misspelled:
6464

65-
```swift
65+
```swift,ignore
6666
func f(x: inout Int) {
6767
whilee x < 10 {
6868
x += 1
@@ -208,12 +208,14 @@ Swift supplies a function for checking that a precondition is upheld,
208208
which can be used as follows:
209209

210210
```swift
211+
# let n = 0
211212
precondition(n >= 0)
212213
```
213214

214215
*or*
215216

216217
```swift
218+
# let n = 0
217219
precondition(n >= 0, "n == \(n); it must be non-negative.")
218220
```
219221

@@ -243,6 +245,8 @@ checks at function boundaries. For example, in the binary search
243245
algorithm mentioned in the previous chapter,
244246

245247
```swift
248+
# var l = 0
249+
# var h = 10
246250
// precondition: l <= h
247251
let m = (h - l) / 2
248252
h = l + m
@@ -258,6 +262,8 @@ help us uncover those flaws during testing of debug builds without
258262
impacting performance of release builds:
259263

260264
```swift
265+
# var l = 0
266+
# var h = 10
261267
assert(l <= h)
262268
let m = (h - l) / 2
263269
h = l + m
@@ -304,11 +310,12 @@ operations must never be turned off in release builds.
304310
extension Array {
305311
/// Exchanges the first and last elements.
306312
mutating func swapFirstAndLast() {
307-
precondition(!self.isEmpty)
308-
if count() == 1 { return } // swapping would be a no-op.
309-
withUnsafeBufferPointer { b in
310-
f = b.baseAddress
311-
l = f + b.count - 1
313+
precondition(!isEmpty)
314+
if count == 1 { return } // swapping would be a no-op.
315+
withUnsafeMutableBufferPointer { b in
316+
guard let base = b.baseAddress else { return }
317+
var f = base
318+
var l = base + (b.count - 1)
312319
swap(&f.pointee, &l.pointee)
313320
}
314321
}
@@ -335,14 +342,14 @@ code in an unfinished state):
335342
/// Returns the number of unused elements when a maximal
336343
/// number of `n`-element chunks are stored in `self`.
337344
func excessWhenFilled(withChunksOfSize n: Int) {
338-
count() % n // n == 0 would violate the precondition of %
345+
count % n // n == 0 would violate the precondition of %
339346
}
340347
}
341348
```
342349

343350
2. Something your function uses can itself report a runtime error:
344351

345-
```swift
352+
```swift,ignore
346353
extension Array {
347354
/// Writes a textual representation of `self` to a temporary file
348355
/// whose location is returned.
@@ -395,11 +402,12 @@ It's appropriate to add a precondition when:
395402
we should instead propagate the error:
396403

397404
```swift
405+
# import Foundation
398406
extension Array {
399407
/// Writes a textual representation of `self` to a temporary file
400408
/// whose location is returned.
401409
func writeToTempFile(withChunksOfSize n: Int) throws -> URL {
402-
let r = FileManager.defaultTemporaryDirectory
410+
let r = FileManager.default.temporaryDirectory
403411
.appendingPathComponent(UUID().uuidString)
404412
try "\(self)".write(to: r, atomically: false, encoding: .utf8)
405413
return r
@@ -435,6 +443,9 @@ features to accommodate it without causing this kind of repeated
435443
boilerplate:
436444

437445
```swift
446+
# func thing1ThatCanFail() -> Result<Int, Error> { .success(0) }
447+
# func thing2ThatCanFail() -> Result<Int, Error> { .success(0) }
448+
# func _resultPropagationDemo() -> Result<Int, Error> {
438449
let someValueOrError = thing1ThatCanFail()
439450
guard case .success(let someValue) = someValueOrError else {
440451
return someValueOrError
@@ -444,13 +455,17 @@ let otherValueOrError = thing2ThatCanFail()
444455
guard case .success(let otherValue) = otherValueOrError else {
445456
return otherValueOrError
446457
}
458+
# return .success(otherValue)
459+
# }
447460
```
448461

449462

450463
Swift's thrown errors fill that role by propagating errors upward with
451464
a simple `try` label on an expression containing the call.
452465

453466
```swift
467+
# func thing1ThatCanFail() throws -> Int { 0 }
468+
# func thing2ThatCanFail() throws -> Int { 0 }
454469
let someValue = try thing1ThatCanFail()
455470
let otherValue = try thing2ThatCanFail()
456471
```
@@ -536,6 +551,8 @@ whose primary home is the summary sentence fragment.[^result-doc]
536551
as though they just return a `T`:
537552

538553
```swift
554+
# import Foundation
555+
# enum IOError: Error {}
539556
extension Array {
540557
/// Writes a textual representation of `self` to a temporary file,
541558
/// returning its location.
@@ -592,18 +609,19 @@ The following code uses that guarantee to ensure that all the
592609
allocated buffers are eventually freed.
593610

594611
```swift
612+
# struct X {}
595613
/// Processes each element of `xs` in an order determined by the
596614
/// [total
597615
/// preorder](https://en.wikipedia.org/wiki/Weak_ordering#Total_preorders)
598616
/// `areInOrder` using a distinct 1Kb buffer for each one.
599617
func f(_ xs: [X], orderedBy areInOrder: (X, X) throws -> Bool) rethrows
600618
{
601619
var buffers = xs.map { x in
602-
(p, UnsafeMutablePointer<UInt8>.allocate(capacity: 1024)) }
603-
defer { for _, b in buffers { b.deallocate() } }
620+
(x, UnsafeMutablePointer<UInt8>.allocate(capacity: 1024)) }
621+
defer { for (_, b) in buffers { b.deallocate() } }
604622

605-
buffers.sort { !areInOrder($1.0, $0.0) }
606-
...
623+
try buffers.sort { !(try areInOrder($1.0, $0.0)) }
624+
// ...
607625
}
608626
```
609627

@@ -656,6 +674,7 @@ making it a precondition that the comparison is a total preorder, we
656674
could weaken the postcondition as follows:
657675

658676
```swift
677+
# extension Array {
659678
/// Sorts the elements so that all adjacent pairs satisfy
660679
/// `areInOrder`, or permutes the elements in an unspecified way if
661680
/// `areInOrder` is not a [total
@@ -665,6 +684,7 @@ could weaken the postcondition as follows:
665684
/// - Complexity: at most N log N comparisons, where N is the number
666685
/// of elements.
667686
mutating func sort(areInOrder: (Element, Element)->Bool) { ... }
687+
# }
668688
```
669689

670690
As you can see, this change makes the API more complicated to no
@@ -783,8 +803,10 @@ manage that is with a `defer` block releasing the resources
783803
immediately after they are allocated:
784804

785805
```swift
786-
let f = try FileHandle(forReadingFrom: p)
787-
defer { f.close() }
806+
# import Foundation
807+
# let p = URL(fileURLWithPath: "/tmp/example.txt")
808+
let f: FileHandle = try .init(forReadingFrom: p)
809+
defer { try? f.close() }
788810
// use f
789811
```
790812

@@ -793,15 +815,16 @@ scope where they were allocated, you can tie them to the `deinit` of
793815
some type:
794816

795817
```swift
818+
# import Foundation
796819
struct OpenFileHandle: ~Copyable {
797820
/// The underlying type with unmanaged close functionality
798821
private let raw: FileHandle
799822

800823
/// An instance for reading from p.
801-
init(forReadingFrom p: URL) { raw = .init(forReadingFrom: p) }
824+
init(forReadingFrom p: URL) { raw = try! .init(forReadingFrom: p) }
802825

803826
deinit {
804-
raw.close()
827+
try? raw.close()
805828
}
806829
}
807830
```
@@ -864,16 +887,20 @@ invariants. For example, imagine a disk-backed version of `PairArray`
864887
from the last chapter, where I/O operations can throw:
865888

866889
```swift
890+
# struct DiskBackedArray<Element> {
891+
# init() {}
892+
# mutating func append(_: Element) throws {}
893+
# }
867894
/// A disk-backed series of `(X, Y)` pairs, where the `X`s and `Y`s
868895
/// are stored in separate files.
869896
struct DiskBackedPairArray<X, Y> {
870897
// Invariant: `xs.count == ys.count`
871898

872899
/// The first part of each element.
873-
private var xs = DiskBackedArray()
900+
private var xs: DiskBackedArray<X> = DiskBackedArray()
874901

875902
/// The second part of each element.
876-
private var ys = DiskBackedArray()
903+
private var ys: DiskBackedArray<Y> = DiskBackedArray()
877904

878905
// ...
879906

better-code/src/chapter-4-algorithms.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ remove a selected shape from an array. The naive implementation is a loop that
2626
scans for the shape and erases it.
2727

2828
```swift
29+
# struct Shape { var isSelected: Bool }
2930
/// Removes the selected shape.
3031
func removeSelected(shapes: inout [Shape]) {
3132
for i in 0..<shapes.count {
@@ -44,6 +45,7 @@ remaining ones, so the code must handle an additional case.
4445

4546
<!-- bad -->
4647
```swift
48+
# struct Shape { var isSelected: Bool }
4749
/// Remove all selected shapes.
4850
func removeAllSelected(shapes: inout [Shape]) {
4951
var i = 0
@@ -63,6 +65,7 @@ subsequent indices are affected by the removal this removes the fix-up.
6365

6466
<!-- bad -->
6567
```swift
68+
# struct Shape { var isSelected: Bool }
6669
/// Remove all selected shapes.
6770
func removeAllSelected(shapes: inout [Shape]) {
6871
for i in (0..<shapes.count).reversed() {
@@ -120,6 +123,7 @@ Both approaches will preserve the relative
120123
order of the unselected shapes. Now we can write the code:
121124

122125
```swift
126+
# struct Shape { var isSelected: Bool }
123127
/// Remove all selected shapes.
124128
func removeAllSelected(shapes: inout [Shape]) {
125129
var p = 0
@@ -208,6 +212,15 @@ extension MutableCollection {
208212
Given `halfStablePartition()` we can rewrite `removeAllSelected()`.
209213

210214
```swift
215+
# struct Shape { var isSelected: Bool }
216+
# extension MutableCollection {
217+
# /// See the full `halfStablePartition` implementation earlier in this chapter.
218+
# mutating func halfStablePartition(
219+
# by belongsInSecondPartition: (Element) -> Bool
220+
# ) -> Index {
221+
# fatalError()
222+
# }
223+
# }
211224
func removeAllSelected(shapes: inout [Shape]) {
212225
shapes.removeSubrange(shapes.halfStablePartition(by: { $0.isSelected })...)
213226
}
@@ -459,7 +472,7 @@ clarity, and enable efficient computation.
459472

460473
<!-- Test to get swift embedded in mdbook, need to build the infrastructure for testing swift code.-->
461474
```swift
462-
{{#include test.swift:2:5}}
475+
{{#include test.swift}}
463476
```
464477

465478

better-code/src/chapter-5-types.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,9 @@ is why `successors_` is private.
127127
The meaning of a type implies a set of operations. For example,
128128

129129
```swift
130+
# struct Point2D {
131+
# let x, y: Float
132+
# }
130133
extension Point2D {
131134
/// Returns the length of the line segment between `self` and
132135
/// `other`.

0 commit comments

Comments
 (0)