iOS Testing Tools
Swift package and agent skill for iOS/macOS UI testing: screenshot capture, Page Object helpers, and multi-peer E2E test coordination.
UITestToolkit
Swift package for iOS/macOS UI testing with screenshot capture and validation.
Products
| Product | Type | Description |
|---|---|---|
| ScreenshotKit | Library | Screenshot capture with structured naming |
| UITestKit | Library | Page Object protocols, XCUIElement extensions, Allure annotations |
| IOSE2EPeerClient | Library | Reusable UI test peer client for generalized E2E coordinator sessions |
| extract-screenshots | CLI | Extract/organize screenshots from xcresult (macOS only) |
| snapshotsdiff | CLI | Create visual diffs between snapshot images (macOS only) |
| ios-device-build | CLI | Build an iOS app across connected physical devices and the Apple Silicon Mac iOS-app destination (macOS only) |
| ios-e2e-runner | CLI | Coordinate generalized multi-peer iOS UI E2E sessions (macOS only) |
| e2e-fake-peer | CLI | Local sample peer for coordinator smoke tests (macOS only) |
| e2e-listener-fake-peer | CLI | Local sample peer for peer-listener transport smoke tests (macOS only) |
Note: Add
ScreenshotKitandUITestKitfor standard UI testing. AddIOSE2EPeerClientonly to UI test targets that participate in generalized E2E coordinator sessions. The CLI tools useFoundation.Process/AppKitwhich are unavailable on iOS, so do NOT add them as target dependencies. Run from terminal only.
Installation
Add to your Package.swift:
dependencies: [
.package(url: "https://github.com/ivalx1s/swift-ui-testing-tools", from: "1.0.0")
// or local: .package(path: "../swift-ui-testing-tools")
]
Add to UI test target:
.testTarget(
name: "YourAppUITests",
dependencies: [
.product(name: "ScreenshotKit", package: "UITestToolkit"),
.product(name: "UITestKit", package: "UITestToolkit"),
// Optional for generalized multi-peer E2E tests:
// .product(name: "IOSE2EPeerClient", package: "UITestToolkit")
]
)
Usage
import XCTest
import ScreenshotKit
import UITestKit
final class MyUITests: BaseUITestSuite {
override class func setUp() {
super.setUp()
ScreenshotManager.shared.startSession()
}
override func setUpWithError() throws {
try super.setUpWithError()
startTestScreenshots()
}
@MainActor
func testLogin() throws {
let app = XCUIApplication()
app.launch()
screenshot(step: 1, "app_launched", app: app)
app.textFields["username"].tap()
app.textFields["username"].typeText("test@example.com")
screenshot(step: 2, "username_entered", app: app)
app.buttons["Login"].tap()
XCTAssertTrue(app.staticTexts["Welcome"].waitFor(\.exists, toBe: true, timeout: 5))
screenshot(step: 3, "login_success", app: app)
}
}
Test Platform Selection
Choose the execution platform from the target declaration before running tests.
- iOS app target or app package: run tests and runtime by default on iOS Simulator.
- App target or app package on another declared platform: run against that declared platform.
- If the app/package declares no platform, default to macOS.
- Package module or multi-platform package: run unit and integration tests on macOS when macOS is supported.
- If macOS is unavailable, prefer iOS when declared.
- Otherwise use the first declared platform.
- Use simulator destinations only for iOS-family runs. For macOS, run on macOS directly.
- If a reusable iOS package is consumed through an app-local graph and standalone package schemes cannot resolve local dependencies, verify it through the app workspace/test host instead of treating the standalone package build as the product build.
Apple Silicon Mac iOS-App Runtime
When an iOS app needs a Mac-side endpoint, use the existing iPhoneOS app on the Apple Silicon Mac Designed for iPad/iPhone destination. Do not add a separate macOS app target unless the product explicitly needs native macOS UI.
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer \
xcodebuild \
-workspace App.xcworkspace \
-scheme App \
-destination 'platform=macOS,arch=arm64,variant=Designed for iPad' \
build
Launching the built Debug-iphoneos/App.app with open is not reliable for this runtime, and XCUITest cannot drive My Mac (Designed for iPad). Use Xcode’s run action through AppleScript to launch the Mac-hosted iOS app, then run the actual UI test against a physical iPhone or Simulator endpoint. See agents/skills/ios-testing-tools/SKILL.md for the full workflow.
Connected Device Builds
Use ios-device-build when a project needs one command that builds an iOS app for every currently discovered physical iPhone/iPad. The command discovers devices through xcrun xcdevice list --json; there is no hard-coded expected device list, so a missing device is skipped and the remaining devices still build. If no matching destinations are found, the command fails.
The build contract is intentionally explicit. Pass the project source, scheme, discovery interfaces, target set, and DerivedData/log root every time. The CLI fails fast on missing required parameters instead of guessing.
Discovery is an interface option set:
--discovery usbbuilds only cable-connected physical iOS devices. This is the preferred stable mode.--discovery wifibuilds only network-discovered physical iOS devices.--discovery usb,wifibuilds every available physical iOS device Xcode currently sees.
Target selection is separate:
--targets iphonesbuilds physical iOS devices.--targets macbookbuilds the Apple Silicon MacDesigned for iPad/iPhonedestination when Xcode exposes it.--targets iphones,macbookbuilds both groups.
./Scripts/ios-device-build.sh \
--workspace App.xcworkspace \
--scheme App \
--discovery usb \
--targets iphones \
--derived-data-root .temp/device-builds
./Scripts/ios-device-build.sh \
--project App.xcodeproj \
--scheme App \
--discovery usb,wifi \
--targets iphones \
--derived-data-root .temp/device-builds
./Scripts/ios-device-build.sh \
--workspace App.xcworkspace \
--scheme App \
--discovery usb \
--targets iphones,macbook \
--mac-destination 'platform=macOS,arch=arm64,variant=Designed for iPad' \
--derived-data-root .temp/device-builds
./Scripts/ios-device-build.sh \
--workspace App.xcworkspace \
--scheme App \
--targets macbook \
--mac-destination 'platform=macOS,arch=arm64,variant=Designed for iPad' \
--derived-data-root .temp/device-builds
Per-destination DerivedData and xcodebuild.log files are written under the required --derived-data-root.
iOS E2E Runner
Use ios-e2e-runner to validate and run generalized multi-peer iOS UI E2E sessions. The runner config is project-neutral: product repositories provide peer mappings, Xcode destinations, selectors, and scenario event names in their own config or test code.
The full config contract, reserved environment keys, and artifact layout are documented in .spec/e2e-coordinator-config-schema.md.
Keep product-specific examples in the consumer project that owns the workflow. Toolkit docs should use neutral peer names, event names, bundle placeholders, and device placeholders.
Dry-run validates the config and prints the exact peer launch plan without building, installing, launching devices, or starting UI tests:
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer \
swift run ios-e2e-runner \
--config Samples/IOSE2ECoordinator/dry-run-two-peer.yaml \
--dry-run \
--session-id sample-session
To run a real coordinator session, use the same config shape without --dry-run. The config must point at reachable physical devices or simulators, valid Xcode projects, and UI test selectors owned by the consumer project:
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer \
swift run ios-e2e-runner \
--config path/to/e2e-physical.yaml \
--session-id physical-validation-001 \
--developer-dir /Applications/Xcode.app/Contents/Developer
The runner injects reserved E2E_* environment values into every peer process so UI tests can connect to the coordinator through IOSE2EPeerClient.
UI test targets that participate in a coordinator session should import IOSE2EPeerClient and create the peer client from the injected environment:
import IOSE2EPeerClient
import XCTest
final class PeerScenarioTests: XCTestCase {
func testScenario() async throws {
let client = try await UITestE2EClient.fromEnvironment()
defer { client.close() }
if client.environment.peerNameValue == "peer-a" {
try await client.publish("peer-a.ready", delivery: .acked())
_ = try await client.waitFor("peer-b.ready", timeout: 30)
} else {
_ = try await client.waitFor("peer-a.ready", timeout: 30)
try await client.publish("peer-b.ready", delivery: .acked())
}
}
}
Physical devices must be able to open TCP connections to the coordinator host. When all peers share the same LAN route, set coordinator.advertisedHost once. When peers need different routes to the same coordinator, for example separate wired or lab-network interfaces, set coordinatorHost on the individual peer; the runner keeps the global coordinator bind/port/path and injects a peer-specific E2E_COORDINATOR_URL.
peers:
- name: peer-a
coordinatorHost: 192.168.50.10
launch:
kind: xctest
startWhen:
type: immediate
Build the local sample peer before running the process-peer sample:
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer \
swift build --product e2e-fake-peer
Then run the three-peer local sample:
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer \
./Scripts/run-e2e-sample-smoke.sh
Peer-Listener Transport
Use coordinator.transport: peer-listener when physical iOS peers cannot reach a Mac-hosted WebSocket coordinator directly. In this mode each UI test peer starts a device-side TCP listener, the Mac runner starts iproxy for each connected device, and the Mac-side coordinator connects to every peer through the forwarded localhost ports.
Consumer UI tests should import IOSE2EPeerClient and use UITestE2EClient.fromEnvironment(). Do not import IOSE2ECoordinatorCore from product tests just to inspect peer names; use client.environment.peerNameValue for project scenario branching.
coordinator:
transport: peer-listener
peers:
- name: peer-a
connection:
listenPort: 19131
connectHost: 127.0.0.1
connectPort: 18131
proxy:
kind: iproxy
udid: 00000000-0000000000000000
Run the toolkit-local smoke:
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer \
./Scripts/run-e2e-peer-listener-sample-smoke.sh
Physical Device Runtime Logs
When debugging runtime behavior on a physical iPhone, prefer the live device console over guessing from xcodebuild output.
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer \
xcrun xcdevice list
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer \
xcrun devicectl device process launch \
--device <device-udid> \
--terminate-existing \
--console \
<bundle-id>
Use --console for app-scoped logs, --terminate-existing to avoid stale processes, and the device UDID rather than the display name.
Scripts
| Script | Description |
|---|---|
check-tools.sh | Verify required tools, plus optional iOS Simulator support |
run-tests-and-extract.sh | Run UI tests + extract screenshots |
extract-screenshots.sh | Extract screenshots from xcresult |
ios-device-build.sh | Build an iOS app across discovered physical devices and optional Mac-compatible iOS destination |
run-e2e-sample-smoke.sh | Build the fake peer, run the local three-peer coordinator sample, and validate artifacts |
run-e2e-peer-listener-sample-smoke.sh | Build the listener fake peer, run the local peer-listener coordinator sample, and validate artifacts |
ios-e2e-runner | SwiftPM executable for generalized multi-peer E2E dry-run and execution |
e2e-fake-peer | SwiftPM executable used by local coordinator samples |
e2e-listener-fake-peer | SwiftPM executable used by local peer-listener samples |
setup.sh | Canonical global skill install into ~/.agents |
setup-project-skills.sh | Install AI skill to a project-local .agents |
setup-global-skills.sh | Compatibility wrapper around ./setup.sh |
# Check prerequisites
./Scripts/check-tools.sh
# Run tests and extract screenshots (run from your project directory!)
cd /path/to/your/project
~/src/swift-ui-testing-tools/Scripts/run-tests-and-extract.sh \
-workspace App.xcworkspace \
-scheme App \
-output .temp/screenshots
Options:
-output dir: where to put screenshots (default:.temp/{timestamp}_screenshots, relative to CWD)-destination "...": simulator to use (default: iPhone 16)
The simulator examples above are for iOS app/UI-test flows. Package module unit and integration tests should stay on macOS when macOS is supported by the package.
Project Structure
agents/skills/ ← source repo content
ios-testing-tools/ ← source skill
~/.agents/skills/ios-testing-tools/ ← global installed runtime copy
<project>/.agents/skills/ios-testing-tools/ ← project-local installed runtime copy
.claude/skills → ../.agents/skills ← symlink for Claude Code
.codex/skills → ../.agents/skills ← symlink for Codex CLI
Source content lives in agents/skills/. Installed runtime copies live in ~/.agents/skills/ or <project>/.agents/skills/, and those copies are degitized after sync. .claude/ and .codex/ only point at the installed copy.
AI Agent Skill
Includes ios-testing-tools skill for AI-assisted UI test development.
Setup:
./setup.sh # canonical global install
./Scripts/setup-project-skills.sh /path/to/your/project # project-local install
./Scripts/setup-global-skills.sh # compatibility wrapper
Provides:
- Page Object pattern templates
- Accessibility ID naming conventions (BEM-like)
- Shared identifiers setup (app + test targets)
- Screenshot workflow with verification
- Snapshot testing with swift-snapshot-testing
- Allure integration (optional)
Snapshot Testing
For automated UI regression testing, use swift-snapshot-testing.
Add to your snapshot test target:
dependencies: [
.package(url: "https://github.com/pointfreeco/swift-snapshot-testing", from: "1.17.0")
]
See the ios-testing-tools skill documentation for detailed patterns and examples.
snapshotsdiff CLI
Create visual diffs between snapshot images:
# Compare two images
swift run snapshotsdiff reference.png failed.png diff.png
# Batch compare all failed snapshots
swift run snapshotsdiff \
--artifacts ./SnapshotArtifacts \
--output ./SnapshotDiffs \
--tests ./AppSnapshotTests
Output for batch mode:
SnapshotDiffs/
TestName/
snapshotName/
├── reference.png (expected)
├── failed.png (actual)
└── diff.png (visual diff)
CI/CD Integration
GitLab CI
# .gitlab-ci.yml
stages:
- build_and_test
- generate_snapshot_diffs
variables:
SNAPSHOT_ARTIFACTS: "$CI_PROJECT_DIR/SnapshotArtifacts"
SNAPSHOT_DIFFS: "$CI_PROJECT_DIR/SnapshotDiffs"
SNAPSHOT_TESTS: "$CI_PROJECT_DIR/AppSnapshotTests"
build_and_test:
stage: build_and_test
script:
- bundle exec fastlane build_and_test
artifacts:
when: always
paths:
- $SNAPSHOT_ARTIFACTS
expire_in: 1 day
generate_snapshot_diffs:
stage: generate_snapshot_diffs
dependencies:
- build_and_test
script:
- |
if [ -d "$SNAPSHOT_ARTIFACTS" ] && [ "$(ls -A $SNAPSHOT_ARTIFACTS)" ]; then
swift run --package-path /path/to/swift-ui-testing-tools snapshotsdiff \
--artifacts "$SNAPSHOT_ARTIFACTS" \
--output "$SNAPSHOT_DIFFS" \
--tests "$SNAPSHOT_TESTS"
fi
artifacts:
when: always
paths:
- $SNAPSHOT_DIFFS
expire_in: 1 week
GitHub Actions
# .github/workflows/snapshot-tests.yml
name: Snapshot Tests
on:
pull_request:
jobs:
test:
runs-on: macos-14
steps:
- uses: actions/checkout@v4
- name: Run tests
run: xcodebuild test -scheme AppSnapshotTests -destination "platform=iOS Simulator,name=iPhone 16" || true
- name: Generate diffs
run: |
git clone https://github.com/user/swift-ui-testing-tools.git /tmp/toolkit
if [ -d "SnapshotArtifacts" ]; then
swift run --package-path /tmp/toolkit snapshotsdiff \
--artifacts ./SnapshotArtifacts --output ./SnapshotDiffs --tests ./AppSnapshotTests
fi
- uses: actions/upload-artifact@v4
with:
name: snapshot-diffs
path: SnapshotDiffs/
Fastlane
# fastlane/Fastfile
lane :generate_snapshot_diffs do
artifacts = ENV['SNAPSHOT_ARTIFACTS']
return unless File.directory?(artifacts) && !Dir.empty?(artifacts)
sh("swift run --package-path ../swift-ui-testing-tools snapshotsdiff " \
"--artifacts #{artifacts} --output #{ENV['SNAPSHOT_DIFFS']} --tests #{ENV['SNAPSHOT_TESTS']}")
end
See ios-testing-tools skill for detailed examples.
Requirements
- iOS 15.0+ / macOS 13.0+
- Swift 6.2+
- Xcode 26+
Run ./Scripts/check-tools.sh to verify.
The Relux stack
This package is part of the Relux stack: the Relux unidirectional data-flow architecture for Swift 6, a family of modules around it, and agent-ready testing tools. The stack is how we build MVPs fast on agentic rails and then scale them into enterprise-grade apps: Tuist workspaces, strict modularization, and a UDF architecture proven in production for years. Browse the full picture in the Relux Works open-source catalog.
About Relux Works
This project is part of the open-source ecosystem of Relux Works, an AI-native software development studio. We build fixed-price MVPs, rescue vibe-coded apps, run local AI inference, and train teams to work with coding agents. Much of the infrastructure behind that work is open source.
- Full catalog: relux.works/en/open-source
- Agentic enablement: agent harnesses & team training
- Hire us the agent-native way: point your assistant at
https://api.relux.works/mcp - Contact: ivan@relux.works
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to the Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by the Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding any notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
Copyright 2026 Alexey Grigorev
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.