Skip to content

Improve testing utils #60

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Apr 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@ let package = Package(
.macOS(.v10_13),
],
products: [
// core library
.library(name: "AWSLambdaRuntime", targets: ["AWSLambdaRuntime"]),
// common AWS events
.library(name: "AWSLambdaEvents", targets: ["AWSLambdaEvents"]),
// for testing only
.library(name: "AWSLambdaTesting", targets: ["AWSLambdaTesting"]),
],
dependencies: [
.package(url: "https://github.com/apple/swift-nio.git", from: "2.8.0"),
Expand All @@ -26,6 +30,12 @@ let package = Package(
.testTarget(name: "AWSLambdaRuntimeTests", dependencies: ["AWSLambdaRuntime"]),
.target(name: "AWSLambdaEvents", dependencies: []),
.testTarget(name: "AWSLambdaEventsTests", dependencies: ["AWSLambdaEvents"]),
// testing helper
.target(name: "AWSLambdaTesting", dependencies: [
"AWSLambdaRuntime",
.product(name: "NIO", package: "swift-nio"),
]),
.testTarget(name: "AWSLambdaTestingTests", dependencies: ["AWSLambdaTesting"]),
// samples
.target(name: "StringSample", dependencies: ["AWSLambdaRuntime"]),
.target(name: "CodableSample", dependencies: ["AWSLambdaRuntime"]),
Expand Down
89 changes: 89 additions & 0 deletions Sources/AWSLambdaTesting/Lambda+Testing.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftAWSLambdaRuntime open source project
//
// Copyright (c) 2020 Apple Inc. and the SwiftAWSLambdaRuntime project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

// @testable for access of internal functions - this would only work for testing by design
@testable import AWSLambdaRuntime
import Dispatch
import Logging
import NIO

extension Lambda {
public struct TestConfig {
public var requestId: String
public var traceId: String
public var invokedFunctionArn: String
public var timeout: DispatchTimeInterval

public init(requestId: String = "\(DispatchTime.now().uptimeNanoseconds)",
traceId: String = "Root=\(DispatchTime.now().uptimeNanoseconds);Parent=\(DispatchTime.now().uptimeNanoseconds);Sampled=1",
invokedFunctionArn: String = "arn:aws:lambda:us-west-1:\(DispatchTime.now().uptimeNanoseconds):function:custom-runtime",
timeout: DispatchTimeInterval = .seconds(5)) {
self.requestId = requestId
self.traceId = traceId
self.invokedFunctionArn = invokedFunctionArn
self.timeout = timeout
}
}

public static func test(_ closure: @escaping StringLambdaClosure,
with payload: String,
using config: TestConfig = .init()) throws -> String {
try Self.test(StringLambdaClosureWrapper(closure), with: payload, using: config)
}

public static func test(_ closure: @escaping StringVoidLambdaClosure,
with payload: String,
using config: TestConfig = .init()) throws {
_ = try Self.test(StringVoidLambdaClosureWrapper(closure), with: payload, using: config)
}

public static func test<In: Decodable, Out: Encodable>(
_ closure: @escaping CodableLambdaClosure<In, Out>,
with payload: In,
using config: TestConfig = .init()
) throws -> Out {
try Self.test(CodableLambdaClosureWrapper(closure), with: payload, using: config)
}

public static func test<In: Decodable>(
_ closure: @escaping CodableVoidLambdaClosure<In>,
with payload: In,
using config: TestConfig = .init()
) throws {
_ = try Self.test(CodableVoidLambdaClosureWrapper(closure), with: payload, using: config)
}

public static func test<In, Out, Handler: EventLoopLambdaHandler>(
_ handler: Handler,
with payload: In,
using config: TestConfig = .init()
) throws -> Out where Handler.In == In, Handler.Out == Out {
let logger = Logger(label: "test")
let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
defer {
try! eventLoopGroup.syncShutdownGracefully()
}
let eventLoop = eventLoopGroup.next()
let context = Context(requestId: config.requestId,
traceId: config.traceId,
invokedFunctionArn: config.invokedFunctionArn,
deadline: .now() + config.timeout,
logger: logger,
eventLoop: eventLoop)

return try eventLoop.flatSubmit {
handler.handle(context: context, payload: payload)
}.wait()
}
}
147 changes: 147 additions & 0 deletions Tests/AWSLambdaTestingTests/Tests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftAWSLambdaRuntime open source project
//
// Copyright (c) 2020 Apple Inc. and the SwiftAWSLambdaRuntime project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import AWSLambdaRuntime
import AWSLambdaTesting
import NIO
import XCTest

class LambdaTestingTests: XCTestCase {
func testCodableClosure() {
struct Request: Codable {
let name: String
}

struct Response: Codable {
let message: String
}

let myLambda = { (_: Lambda.Context, request: Request, callback: (Result<Response, Error>) -> Void) in
callback(.success(Response(message: "echo" + request.name)))
}

let request = Request(name: UUID().uuidString)
var response: Response?
XCTAssertNoThrow(response = try Lambda.test(myLambda, with: request))
XCTAssertEqual(response?.message, "echo" + request.name)
}

func testCodableVoidClosure() {
struct Request: Codable {
let name: String
}

let myLambda = { (_: Lambda.Context, _: Request, callback: (Result<Void, Error>) -> Void) in
callback(.success(()))
}

let request = Request(name: UUID().uuidString)
XCTAssertNoThrow(try Lambda.test(myLambda, with: request))
}

func testLambdaHandler() {
struct Request: Codable {
let name: String
}

struct Response: Codable {
let message: String
}

struct MyLambda: LambdaHandler {
typealias In = Request
typealias Out = Response

func handle(context: Lambda.Context, payload: In, callback: @escaping (Result<Out, Error>) -> Void) {
XCTAssertFalse(context.eventLoop.inEventLoop)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

callback(.success(Response(message: "echo" + payload.name)))
}
}

let request = Request(name: UUID().uuidString)
var response: Response?
XCTAssertNoThrow(response = try Lambda.test(MyLambda(), with: request))
XCTAssertEqual(response?.message, "echo" + request.name)
}

func testEventLoopLambdaHandler() {
struct MyLambda: EventLoopLambdaHandler {
typealias In = String
typealias Out = String

func handle(context: Lambda.Context, payload: String) -> EventLoopFuture<String> {
XCTAssertTrue(context.eventLoop.inEventLoop)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

return context.eventLoop.makeSucceededFuture("echo" + payload)
}
}

let input = UUID().uuidString
var result: String?
XCTAssertNoThrow(result = try Lambda.test(MyLambda(), with: input))
XCTAssertEqual(result, "echo" + input)
}

func testFailure() {
struct MyError: Error {}

struct MyLambda: LambdaHandler {
typealias In = String
typealias Out = Void

func handle(context: Lambda.Context, payload: In, callback: @escaping (Result<Out, Error>) -> Void) {
callback(.failure(MyError()))
}
}

XCTAssertThrowsError(try Lambda.test(MyLambda(), with: UUID().uuidString)) { error in
XCTAssert(error is MyError)
}
}

func testAsyncLongRunning() {
var executed: Bool = false
let myLambda = { (_: Lambda.Context, _: String, callback: @escaping (Result<Void, Error>) -> Void) in
DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 0.5) {
executed = true
callback(.success(()))
}
}

XCTAssertNoThrow(try Lambda.test(myLambda, with: UUID().uuidString))
XCTAssertTrue(executed)
}

func testConfigValues() {
let timeout: TimeInterval = 4
let config = Lambda.TestConfig(
requestId: UUID().uuidString,
traceId: UUID().uuidString,
invokedFunctionArn: "arn:\(UUID().uuidString)",
timeout: .seconds(4)
)

let myLambda = { (ctx: Lambda.Context, _: String, callback: @escaping (Result<Void, Error>) -> Void) in
XCTAssertEqual(ctx.requestId, config.requestId)
XCTAssertEqual(ctx.traceId, config.traceId)
XCTAssertEqual(ctx.invokedFunctionArn, config.invokedFunctionArn)

let secondsSinceEpoch = Double(Int64(bitPattern: ctx.deadline.rawValue)) / -1_000_000_000
XCTAssertEqual(Date(timeIntervalSince1970: secondsSinceEpoch).timeIntervalSinceNow, timeout, accuracy: 0.1)

callback(.success(()))
}

XCTAssertNoThrow(try Lambda.test(myLambda, with: UUID().uuidString, using: config))
}
}