Skip to content

Commit

Permalink
Set up the repository with the Truth, StringUtils and OptionalUtils m…
Browse files Browse the repository at this point in the history
…odules. (#3)
  • Loading branch information
cgrindel authored Jul 16, 2021
1 parent 73d3e25 commit 74ad0c8
Show file tree
Hide file tree
Showing 71 changed files with 3,113 additions and 0 deletions.
1 change: 1 addition & 0 deletions .bazelversion
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
4.1.0
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Ignore Bazel symlinks
bazel-*

8 changes: 8 additions & 0 deletions Sources/OptionalUtils/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")

swift_library(
name = "OptionalUtils",
srcs = glob(["**/*.swift"]),
module_name = "OptionalUtils",
visibility = ["//visibility:public"],
)
9 changes: 9 additions & 0 deletions Sources/OptionalUtils/IsNotNil.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
public extension Optional {
/// Unwraps the optional
func isNotNil(file: StaticString = #file, line: UInt = #line) throws -> Wrapped {
guard let wrapped = self else {
throw OptionalError.unexpectedNil(file, line)
}
return wrapped
}
}
4 changes: 4 additions & 0 deletions Sources/OptionalUtils/OptionalError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
enum OptionalError: Error {
/// First arg is the source filename, second arg is the line number
case unexpectedNil(StaticString, UInt)
}
36 changes: 36 additions & 0 deletions Sources/OptionalUtils/OptionalProtocol.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Loving inspired by
// https://stackoverflow.com/questions/27989094/how-to-unwrap-an-optional-value-from-any-type/32516815.

public protocol OptionalProtocol {
associatedtype Wrapped

func isSome() -> Bool
func isNone() -> Bool
func wrappedValue() throws -> Wrapped
}

extension Optional: OptionalProtocol {
enum OptionalProtocolError: Error {
case noValue
}

public func isSome() -> Bool {
switch self {
case .none: return false
case .some: return true
}
}

public func isNone() -> Bool {
return !isSome()
}

public func wrappedValue() throws -> Wrapped {
switch self {
case .none:
throw OptionalProtocolError.noValue
case let .some(wrappedValue):
return wrappedValue
}
}
}
8 changes: 8 additions & 0 deletions Sources/StringUtils/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")

swift_library(
name = "StringUtils",
srcs = glob(["**/*.swift"]),
module_name = "StringUtils",
visibility = ["//visibility:public"],
)
24 changes: 24 additions & 0 deletions Sources/StringUtils/Data.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import Foundation

enum DataStringConversionError: Error {
case failedToConvertDataToString(String.Encoding)
case failedToConvertStringToData(String.Encoding)
}

extension Data {
public func toString(using encoding: String.Encoding = .utf8) throws -> String {
guard let result = String(data: self, encoding: encoding) else {
throw DataStringConversionError.failedToConvertDataToString(encoding)
}
return result
}
}

extension String {
public func toData(using encoding: String.Encoding = .utf8) throws -> Data {
guard let result = data(using: encoding) else {
throw DataStringConversionError.failedToConvertStringToData(encoding)
}
return result
}
}
5 changes: 5 additions & 0 deletions Sources/StringUtils/StaticString.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
extension StaticString {
public func toString() -> String {
withUTF8Buffer { String(decoding: $0, as: UTF8.self) }
}
}
24 changes: 24 additions & 0 deletions Sources/StringUtils/String+SubstringExt.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Adapted from https://stackoverflow.com/questions/39677330/how-does-string-substring-work-in-swift
// Note: Using integers is not perfect. However, it makes some manipulations much easier to grok.

public extension String {
func index(from offset: Int) -> Index {
return index(startIndex, offsetBy: offset)
}

func substring(from idx: Int) -> Substring {
let fromIndex = index(from: idx)
return self[fromIndex...]
}

func substring(to idx: Int) -> Substring {
let toIndex = index(from: idx)
return self[..<toIndex]
}

func substring(with range: Range<Int>) -> Substring {
let startIndex = index(from: range.lowerBound)
let endIndex = index(from: range.upperBound)
return self[startIndex ..< endIndex]
}
}
36 changes: 36 additions & 0 deletions Sources/StringUtils/StringRandom.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
extension String {
// Lovingly inspired by https://stackoverflow.com/questions/26845307/generate-random-alphanumeric-string-in-swift

/// Specifies the type of random string that should be generated.
public enum RandomStringMode {
case upperAlpha
case lowerAlpha
case upperAlphaNumeric
case lowerAlphaNumeric
case alpha
case alphaNumeric

/// Returns the characters that should be used for the mode.
public var letters: String {
switch self {
case .upperAlpha:
return "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
case .lowerAlpha:
return "abcdefghijklmnopqrstuvwxyz"
case .upperAlphaNumeric:
return "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
case .lowerAlphaNumeric:
return "abcdefghijklmnopqrstuvwxyz0123456789"
case .alpha:
return "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
case .alphaNumeric:
return "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
}
}
}

/// Generates a random string with the specified length and the specified contents.
public static func random(length: Int, mode: RandomStringMode) -> String {
return String((0 ..< length).map { _ in mode.letters.randomElement()! })
}
}
88 changes: 88 additions & 0 deletions Sources/Truth/AssertThat.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import XCTest

/// Return a subject for the target.
public func assertThat<T>(
_ actual: T,
using failureStrategy: FailureStrategy,
with customMessage: MessageClosure? = nil
) -> Subject<T> {
var customMessages = [MessageClosure]()
if let customMessage = customMessage {
customMessages.append(customMessage)
}
return Subject(actual: actual, failureStrategy: failureStrategy, customMessages: customMessages)
}

// Create a subject for the target and pass it to the provided consumer. This form is useful when
// wanting to perform a number of assertions on a single subject. This avoids massive compilation
// times when chaining assertions.
@discardableResult
public func assertThat<T>(
_ actual: T,
using failureStrategy: FailureStrategy,
with customMessage: MessageClosure? = nil,
_ consumer: (Subject<T>) -> Void
) -> Subject<T> {
let subject = assertThat(actual, using: failureStrategy, with: customMessage)
consumer(subject)
return subject
}

/// Return a subject for the throwable expression.
public func assertThat<T>(
using failureStrategy: FailureStrategy,
_ expression: @escaping () throws -> T
) -> ThrowsSubject<T> {
return ThrowsSubject(failureStrategy: failureStrategy, expression: expression)
}

extension XCTestCase {
/// Return a subject for the target.
public func assertThat<T>(_ actual: T, with customMessage: MessageClosure? = nil) -> Subject<T> {
return Truth.assertThat(actual, using: failureStrategy, with: customMessage)
}

// Create a subject for the target and pass it to the provided consumer. This form is useful when
// wanting to perform a number of assertions on a single subject. This avoids massive compilation
// times when chaining assertions.
@discardableResult
public func assertThat<T>(
_ actual: T,
with customMessage: MessageClosure? = nil,
_ consumer: (Subject<T>) -> Void
) -> Subject<T> {
return Truth.assertThat(actual, using: failureStrategy, with: customMessage, consumer)
}

/// Return a subject for the throwable expression.
public func assertThat<T>(
expression: @escaping () throws -> T
) -> ThrowsSubject<T> {
return Truth.assertThat(using: failureStrategy, expression)
}
}

// MARK: - Helpers for Child Subjects

extension BaseSubject {
/// Returns a Subject for the provided value using this instance's failure strategy.
public func that<T>(_ actual: T) -> Subject<T> {
return Subject(
actual: actual,
failureStrategy: failureStrategy,
customMessages: customMessages,
continueAssertions: continueAssertions
)
}

/// Returns a Subject for the provided expression using this instance's failure strategy.
public func that<T>(
expression: @escaping ThrowsSubject<T>.ThrowableFn
) -> ThrowsSubject<T> {
return ThrowsSubject(
failureStrategy: failureStrategy,
continueAssertions: continueAssertions,
expression: expression
)
}
}
13 changes: 13 additions & 0 deletions Sources/Truth/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")

swift_library(
name = "Truth",
testonly = 1,
srcs = glob(["**/*.swift"]),
module_name = "Truth",
visibility = ["//visibility:public"],
deps = [
"//Sources/OptionalUtils",
"//Sources/StringUtils",
],
)
28 changes: 28 additions & 0 deletions Sources/Truth/BidirectionalCollectionAssertions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
public extension Subject where T: BidirectionalCollection {
/// Provides a means for executing assertions on the last element of the array. This method will
/// return the collection subject for additional assertions.
@discardableResult func lastItem<E>(
file: StaticString = #file,
line: UInt = #line,
_ elemSubjectConsumer: (Subject<E>) throws -> Void
) -> Self where E == T.Element {
guard continueAssertions else {
return self
}
guard let element = actual.last else {
fail(
file: file,
line: line,
Fact("expected the last element to exist")
)
return self
}
do {
let elemSubject = that(element)
try elemSubjectConsumer(elemSubject)
} catch {
fail(file: file, line: line, .unexpectedError(error))
}
return self
}
}
39 changes: 39 additions & 0 deletions Sources/Truth/BinaryIntegerAssertions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import Foundation

extension Subject where T: BinaryInteger {
/// Asserts that the actual value is less than the expected value.
@discardableResult public func isLessThan(
_ expected: T,
file: StaticString = #file,
line: UInt = #line
) -> Self {
return doAssert(
failWith: Failure(
file: file,
line: line,
Fact("expected to be less than", expected),
Fact("but was", actual)
)
) {
return actual < expected
}
}

/// Asserts that the actual value is greater than the expected value.
@discardableResult public func isGreaterThan(
_ expected: T,
file: StaticString = #file,
line: UInt = #line
) -> Self {
return doAssert(
failWith: Failure(
file: file,
line: line,
Fact("expected to be greater than", expected),
Fact("but was", actual)
)
) {
return actual > expected
}
}
}
15 changes: 15 additions & 0 deletions Sources/Truth/BoolAssertions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
extension Subject where T == Bool {
/// Asserts that the actual value is true.
public func isTrue(file: StaticString = #file, line: UInt = #line) {
doAssert(failWith: Failure(file: file, line: line, Fact("expected to be true"))) {
actual
}
}

/// Asserts that the actual value is false.
public func isFalse(file: StaticString = #file, line: UInt = #line) {
doAssert(failWith: Failure(file: file, line: line, Fact("expected to be false"))) {
!actual
}
}
}
Loading

0 comments on commit 74ad0c8

Please sign in to comment.