Skip to content

Commit

Permalink
[WIP] Add integration tests
Browse files Browse the repository at this point in the history
  • Loading branch information
slashmo committed Oct 5, 2024
1 parent 81a3eb9 commit c47bd17
Show file tree
Hide file tree
Showing 10 changed files with 233 additions and 2 deletions.
43 changes: 43 additions & 0 deletions .github/workflows/integration.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: "Integration Tests"
on: [push, pull_request]
jobs:
integration_test:
name: Integration Tests
runs-on: ubuntu-latest

strategy:
fail-fast: false
matrix:
swift: ["5.9", "5.10", "latest"]

steps:
- name: Install Swift
uses: vapor/swiftly-action@v0.1
with:
toolchain: ${{ matrix.swift }}
env:
SWIFTLY_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Checkout
uses: actions/checkout@v4.2.0

- name: Resolve Swift dependencies
run: |
cd IntegrationTests
swift package resolve
- name: Start OTel Collector
run: |
cd IntegrationTests
docker compose -f docker/docker-compose.yaml up -d
- name: Wait for OTel Collector
uses: iFaxity/wait-on-action@v1.2.1
with:
resource: "file:./IntegrationTests/docker/otel-collector-output/output.jsonl"
timeout: 3000

- name: Run Integration Tests
run: |
cd IntegrationTests
OTEL_COLLECTOR_OUTPUT=$(pwd)/docker/otel-collector-output swift test
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ xcuserdata/
.protoc-grpc-swift-plugins.download/
swift-otel-workspace.xcworkspace/
.benchmarkBaselines
IntegrationTests/docker/otel-collector-output/
22 changes: 22 additions & 0 deletions IntegrationTests/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// swift-tools-version:5.9
import PackageDescription

let package = Package(
name: "swift-otel-integration-tests",
dependencies: [
.package(path: "../"),
.package(url: "https://github.com/swift-server/swift-service-lifecycle.git", from: "2.0.0"),
],
targets: [
.testTarget(
name: "IntegrationTests",
dependencies: [
.product(name: "OTel", package: "swift-otel"),
.product(name: "OTLPGRPC", package: "swift-otel"),
.product(name: "ServiceLifecycle", package: "swift-service-lifecycle"),
],
swiftSettings: [.enableExperimentalFeature("StrictConcurrency=complete")]
),
],
swiftLanguageVersions: [.version("6"), .v5]
)
133 changes: 133 additions & 0 deletions IntegrationTests/Tests/IntegrationTests/OTLPGRPCIntegrationTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift OTel open source project
//
// Copyright (c) 2024 Moritz Lang and the Swift OTel project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import Foundation
@testable import Instrumentation
@testable import Logging
import NIO
import OTel
import OTLPGRPC
import ServiceLifecycle
import W3CTraceContext
import XCTest

final class OTLPGRPCIntegrationTests: XCTestCase, @unchecked Sendable {
func test_example() async throws {
LoggingSystem.bootstrapInternal { label in
var handler = StreamLogHandler.standardOutput(label: label)
handler.logLevel = .trace
return handler
}
let logger = Logger(label: "test")
let group = MultiThreadedEventLoopGroup.singleton
let exporter = try OTLPGRPCSpanExporter(
configuration: OTLPGRPCSpanExporterConfiguration(environment: [:]),
group: group,
requestLogger: logger,
backgroundActivityLogger: logger
)
let processor = OTelBatchSpanProcessor(
exporter: exporter,
configuration: .init(
environment: [:],
scheduleDelay: .zero
)
)
let tracer = OTelTracer(
idGenerator: OTelRandomIDGenerator(),
sampler: OTelConstantSampler(isOn: true),
propagator: OTelW3CPropagator(),
processor: processor,
environment: [:],
resource: OTelResource(attributes: ["service.name": "IntegrationTests"])
)

InstrumentationSystem.bootstrapInternal(tracer)

let serviceGroup = ServiceGroup(
configuration: .init(
services: [
.init(service: tracer),
.init(service: TestService(), successTerminationBehavior: .gracefullyShutdownGroup),
],
logger: logger
)
)
try await serviceGroup.run()
}
}

struct TestService: Service {
func run() async throws {
let otelCollectorOutputPath = try XCTUnwrap(ProcessInfo.processInfo.environment["OTEL_COLLECTOR_OUTPUT"])
let outputFileURL = URL(fileURLWithPath: otelCollectorOutputPath).appendingPathComponent("output.jsonl")
XCTAssertTrue(FileManager.default.fileExists(atPath: outputFileURL.path), outputFileURL.path)

let span = InstrumentationSystem.tracer.startSpan("test")
span.attributes["foo"] = "bar"
span.setStatus(.init(code: .ok))
span.end()

// wait for export
try await Task.sleep(for: .seconds(2))

let jsonDecoder = JSONDecoder()
let outputFileContents = try String(contentsOf: outputFileURL).trimmingCharacters(in: .whitespacesAndNewlines)
let lines = outputFileContents.components(separatedBy: .newlines)
let exportLine = try XCTUnwrap(lines.last)
let decodedExportLine = try jsonDecoder.decode(ExportLine.self, from: Data(exportLine.utf8))
let resourceSpans = try XCTUnwrap(decodedExportLine.resourceSpans.first)
let scopeSpans = try XCTUnwrap(resourceSpans.scopeSpans.first)
let exportedSpan = try XCTUnwrap(scopeSpans.spans.first)

XCTAssertEqual(exportedSpan.spanID, span.context.spanContext?.spanID.description)
XCTAssertEqual(exportedSpan.traceID, span.context.spanContext?.traceID.description)
XCTAssertEqual(exportedSpan.name, "test")
XCTAssertEqual(exportedSpan.attributes, [.init(key: "foo", value: .init(stringValue: "bar"))])
}
}

struct ExportLine: Decodable {
let resourceSpans: [ResourceSpan]

struct ResourceSpan: Decodable {
let scopeSpans: [ScopeSpans]

struct ScopeSpans: Decodable {
let spans: [Span]

struct Span: Decodable {
let traceID: String
let spanID: String
let name: String
let attributes: [Attribute]

private enum CodingKeys: String, CodingKey {
case traceID = "traceId"
case spanID = "spanId"
case name
case attributes
}

struct Attribute: Decodable, Equatable {
let key: String
let value: Value

struct Value: Decodable, Equatable {
let stringValue: String
}
}
}
}
}
}
12 changes: 12 additions & 0 deletions IntegrationTests/docker/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
name: swift-otel-integration-tests
services:
otel-collector:
image: otel/opentelemetry-collector-contrib:latest
command: ["--config=/etc/otel-collector-config.yaml"]
volumes:
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
- ./otel-collector-output:/etc/otel-collector-output
ports:
- "4317:4317" # OTLP gRPC receiver

# yaml-language-server: $schema=https://raw.githubusercontent.com/compose-spec/compose-spec/master/schema/compose-spec.json
17 changes: 17 additions & 0 deletions IntegrationTests/docker/otel-collector-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
receivers:
otlp:
protocols:
grpc:
endpoint: "otel-collector:4317"

exporters:
file:
path: /etc/otel-collector-output/output.jsonl

service:
pipelines:
traces:
receivers: [otlp]
exporters: [file]

# yaml-language-server: $schema=https://raw.githubusercontent.com/srikanthccv/otelcol-jsonschema/main/schema.json
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,9 @@ define contents_xcworkspacedata
<Group location="container:Benchmarks" name="Benchmarks">
<FileRef location="group:." name="benchmarks"></FileRef>
</Group>
<Group location="container:IntegrationTests" name="IntegrationTests">
<FileRef location="group:." name="integration-tests"></FileRef>
</Group>
</Workspace>
endef
export contents_xcworkspacedata
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,6 @@ public actor OTelBatchSpanProcessor<Exporter: OTelSpanExporter, Clock: _Concurre
private func export(_ batch: some Collection<OTelFinishedSpan> & Sendable) async {
let batchID = batchID
self.batchID += 1
print(batchID)

var exportLogger = logger
exportLogger[metadataKey: "batch_id"] = "\(batchID)"
Expand Down
2 changes: 1 addition & 1 deletion scripts/validate_format.sh
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ printf "=> Checking format\n"
FIRST_OUT="$(git status --porcelain)"
# swiftformat does not scale so we loop ourselves
shopt -u dotglob
find Sources/* Tests/* Examples/* Benchmarks/* -type d -not -path "*/Generated*" | while IFS= read -r d; do
find Sources/* Tests/* Examples/* Benchmarks/* IntegrationTests/* -type d -not -path "*/Generated*" | while IFS= read -r d; do
printf " * checking $d... "
out=$(mint run swiftformat -quiet $d 2>&1)
if [[ $out == *$'\n' ]]; then
Expand Down
1 change: 1 addition & 0 deletions scripts/validate_license_headers.sh
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ EOF
find . \
\( \! -path './.build/*' \) -a \
\( \! -path './Benchmarks/.build/*' \) -a \
\( \! -path './IntegrationTests/.build/*' \) -a \
\( \! -path '*/Generated/*' \) -a \
\( "${matching_files[@]}" \) -a \
\( \! \( "${exceptions[@]}" \) \) | while read line; do
Expand Down

0 comments on commit c47bd17

Please sign in to comment.