Coder Social home page Coder Social logo

21gramconsulting / beton Goto Github PK

View Code? Open in Web Editor NEW
27.0 3.0 1.0 385 KB

An enhancement built on top of Foundation Framework and XCTest. Performance test assertions and the magic ?! operator among many. We primarily use it for server-side engineering.

Home Page: https://21gramconsulting.github.io/Beton/

License: MIT License

Swift 100.00%
swift-server server-side-swift swift swift-library swift-libraries swift5 performance-testing performance-metrics swift-package-manager swift-on-server

beton's Introduction

Beton

Beton is a Swift library built on top of the Foundation framework, that provides an additional layer of functionality, including easy localization, performance test measurement support, and convenience functionality. For us, Beton is primarily, but not exclusively, useful for server-side Swift engineering.

Modules

  • Beton: Generic purpose functionalities that may be useful for every application.
  • XCTBeton: Extends the capabilities of XCTest by providing assertions for performance measurements.

Using the Beton Module

Importing

To use Beton simply import it. If you need anything from Foundation you do not need to explicitly import it. You get it for free by importing Beton.

import Beton

Convenience API for Bundle

Using Beton it is quite easy to get localized bundles and values from them.

Suppose you have a localization bundle in your project for the hu_HU locale, with a translation for "Apple" = "Alma" , but you don't have one for "Banana" (which would be "Banán"). The following example finds the bundle, gets the localized version of "Apple", and falls back to the given key "Banana".

let bundle          = Bundle.module.localizationBundles["hu_HU"]
let localizedApple  = bundle?.localizedString("Apple")
// localizedApple == "Alma"
let localizedBanana = bundle?.localizedString("Banana")
// localizedBanana == "Banana"

Convenience API for Locale

Locales in Beton are expressible by string literals.

let locales: [Locale] = ["en_US", "en_GB", "hu_HU"]
for locale in locales {
  print("Currency symbol: \(locale.currencySymbol ?? "N/A")")
}
// Prints:
// Currency symbol: $
// Currency symbol: £
// Currency symbol: Ft

?! operator

The ?! operator unwraps an Optional value if is not nil, otherwise throws the given error.

struct GenericError: Error {}

let answer = try Int("42") ?! GenericError()
// answer == 42

try Int("NaN") ?! GenericError()
// Throws: GenericError()

sum extension on Sequences

Calculates the total of all elements in a sequence. Available on any sequence with values that conform to AdditiveArithmetic

let arraySum = [1.1, 2.2, 3.3, 4.4, 5.5].sum()
// arraySum == 16.5

let rangeSum = (1..<10).sum()
// rangeSum == 45

let setSum = Set(arrayLiteral: 1, 2, 3, 2, 3).sum()
// setSum == 6

Convenience for Measurements

In Beton measurements have default units and they conform to AdditiveArithmetic.

let sum = [1, 2, 3].map { Measurement<UnitLength>(value: $0, unit: .default) }.sum()
// sum == 6.0 m

Using the XCTBeton Module

Let's say you have a simple performance test that measures some code. Using XCTest there is no easy, straightforward way to make assertions to the performance results.

import XCTest

class PerformanceTests: XCTestCase {
  func test_measureSum() {
    measure {
      let _ = (1..<1000).reduce(0, +)
    }
    // Performance assertions needed!
  }
}

You can turn this code into an XCTBeton test by simply changing the import. Yes, that's it. You can now make assertions!

import XCTBeton

class PerformanceTests: XCTestCase {
  func test_measureSum() {
    measure {
      let _ = (1..<1000).reduce(0, +)
    }
    XCTAssertMetric(.clock, .timeMonotonic, .average(maximum: 0.001))
  }
}

If you want to control the type of measurements, and how many times the tests run you can do that using the same API as you would in regular XCTest.

import XCTBeton

class PerformanceTests: XCTestCase {
  func test_measureSum() {
    let options = XCTMeasureOptions()
    options.iterationCount = 100
    measure(metrics: [XCTCPUMetric(), XCTMemoryMetric()], options: options) {
      let _ = (1..<1000).reduce(0, +)
    }
    XCTAssertMetric(.cpu, .time, .average(maximum: 0.002))
    XCTAssertMetric(.cpu, .cycles, .average(maximum: 2000))
    XCTAssertMetric(.memory, .physical, .average(maximum: 20))
  }
}

Adding Beton as a Dependency

To use the Beton library in a SwiftPM project, add it to the dependencies for your package and your target. Your target can depend on either the Beton or XCTBeton modules, or both.

// swift-tools-version:5.5.0

import PackageDescription

let package = Package(
  name: "MyApplication",
  dependencies: [
    .package(url: "https://github.com/21GramConsulting/Beton", from: "1.0.0"),
  ],
  targets: [
    .target(name: "MyApplication", dependencies: [
      .product(name: "Beton", package: "Beton"),
      .product(name: "XCTBeton", package: "Beton"),
    ])
  ]
)

beton's People

Contributors

adam-rocska avatar calyd avatar rlegmann avatar sherlouk avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

Forkers

sherlouk

beton's Issues

Revenge of the Splat

Swift had tuple Splatting, which was awesome.
Now we don't have it, which sux.

I've been missing it for years now as I have it on my standard toolbelt / programming logic coming from different languages.

I have no clue who this genius is on Swift Forums, but this comment gave me the idea below:

import Foundation

infix operator |> : MultiplicationPrecedence
func |> <Input, Output>(
  input: Input,
  lambda: (Input) -> Output
) -> Output { lambda(input) }

func call<Args, Result>(with args: Args, function: (Args) -> Result) -> Result {
  function(args)
}

func add(_ a: Int, _ b: Int) -> Int { a + b }
// this still compiles today
let input = (40, 2)
print(call(with: input, function: add))

print(input |> add)

Command Piping

So, F# has this awesome syntax of

doThis
  |> doThat
  |> thenThat
  |> finallyThat

I've always wanted to have something like this in Swift.

Now finally there is a use case for it (besides it being cool): Operations, CI, that kind of stuff, when you pipe commands into commands into commands!

Similar to our invertible library in TS.

Bundle locale access

import Beton
import SwiftUI

extension Bundle {
  subscript(_ locale: Locale) -> Bundle? {
    var regionMatches: [Bundle] = []
    var languageMatches: [Bundle] = []

    for (knownLocale, bundle) in localizationBundles {
      if knownLocale == locale { return bundle }
      if knownLocale.region == locale.region { regionMatches.append(bundle) }
      if knownLocale.language == locale.language { languageMatches.append(bundle) }
    }

    return regionMatches.first ?? languageMatches.first
  }

  static var ofCurrentLocale: Bundle? {
    if let result = Bundle.module[Locale.current] { return result }
    if let result = Bundle.main[Locale.current] { return result }
    return nil
  }
}

Cartesian Product based testing

At 21Gram for our TypeScript stuff we have a utility function that takes a recursive/nested set of test values and provides a callback that runs your test code based on the cartesian product of the inputs. It's pretty dope.

It'd be nice to have something like it in Swift too.

CSV Convenience

Add a better, professional and well tested version of this quick hackery I made a few days ago.

The idea is that anything that is:

  • a RangeReplacableCollection,
  • who's elements are RangeReplacableCollections
  • who's subitems are ExpressibleByStringLiteral

can all be initialized from a CSV.

The additional stuff to add:

  • Anything that is expressible by an array literal
  • Bunch of tests
  • Bunch of examples
import Foundation

typealias RawCsvTable = [[String]]

extension RangeReplaceableCollection
where
  Element: RangeReplaceableCollection,
  Element.Element: ExpressibleByStringLiteral,
  Element.Element.StringLiteralType == String
{

  typealias CsvTable = Self
  typealias CsvRow = Element
  typealias CsvCell = Element.Element

  init?(fromCsv url: URL) {
    guard let raw = try? String(contentsOf: url) else { return nil }
    self.init(
      raw
        .components(separatedBy: .newlines)
        .map { .init($0.components(separatedBy: ";").map { CsvCell(stringLiteral: $0) }) }
    )
  }

}

Add new Optional Operators

New optional operators like +? -? and so on.

These operators are to save the bunch of boilerplate when for example concatenating a string with an optional string or adding a number with an optional number.

Throwing unwrap

Force unwrap that doesn’t fuck your software.
We need a suffix utility function for this

Add iOS Support

Yo awesome package, I especially love the XCT extensions you've created. I wonder if you'd consider adding iOS support? 👀
It does look like it might compile if we just set the minimum OS version to v13 (instead of the default v9).

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.