Minimal cross-runtime test runner. Zero dependencies.
Note: This project is in beta. APIs may change.
Works identically on Node.js, Deno, Bun, Cloudflare Workers, and browsers.
- No magic. Tests are just modules. Import, register, done.
- No workers. Single process, no startup overhead. Tests run in ~5ms, not ~500ms.
- No transforms. Requires
.mjsor runtime-native TypeScript. No babel, no esbuild. - No config files. CLI args only. Zero configuration to start.
- Isolation is your responsibility. The runner doesn't sandbox tests. Use
beforeEach/afterEachfor setup/teardown. If tests share mutable state, fix the tests, not the runner. - Performance by default. Sequential execution is safe.
describe.concurrentis opt-in for when you know tests are independent.
npm install @igorjs/pure-test --save-devimport { describe, it, expect } from '@igorjs/pure-test'
describe('math', () => {
it('adds numbers', () => {
expect(1 + 1).toBe(2)
})
it('works async', async () => {
const result = await Promise.resolve(42)
expect(result).toBe(42)
})
})
// Auto-runs when the module finishes loading. No run() call needed.Run with any runtime:
node tests/math.test.mjs
deno run --allow-all tests/math.test.mjs
bun tests/math.test.mjspure-test [paths...] [options]
Options:
--reporter, -r <name> Output format: spec (default), tap, json, minimal
--grep, -g <pattern> Run only tests matching pattern (regex)
--testNamePattern, -t Alias for --grep (Jest/Vitest compatible)
--help, -h Show help
Discovers: *.test.mjs, *.test.js, *.spec.mjs, *.spec.js
The CLI imports all discovered test files in a single process, then runs everything once. No workers, no transforms, no config.
npx pure-test tests/ # discover and run all tests
npx pure-test tests/ --reporter tap # TAP output
npx pure-test tests/ --reporter json # JSON output
npx pure-test tests/ --reporter minimal # dots output
npx pure-test tests/math.test.mjs # single file
npx pure-test tests/ --grep "auth" # only tests matching "auth"
npx pure-test tests/ -t "User.*login" # regex pattern (Jest/Vitest compatible)describe(name, fn) // define a suite (nestable)
describe.concurrent(name, fn) // suite with parallel test execution
describe.skip(name, fn) // skip a suite
describe.only(name, fn) // focus: only run this suite
it(name, fn, options?) // define a test (sync or async)
test(name, fn, options?) // alias for it
it.skip(name, fn) // skip a test
it.only(name, fn) // focus: only run this test
it.todo(name) // placeholder for a planned test
it.each(cases)(name, fn) // parameterised test from databeforeAll(fn) // run once before all tests in the suite
afterAll(fn) // run once after all tests in the suite
beforeEach(fn) // run before each test in the suite
afterEach(fn) // run after each test in the suiteHooks inherit: a beforeEach in an outer describe runs before each test in all nested describe blocks.
The third parameter to it() / test() accepts a timeout (number) or an options object:
// Timeout: fail if the test takes longer than 5 seconds
it('slow operation', async () => { ... }, 5000)
// Retry: re-run a flaky test up to 3 times before failing
it('flaky API call', async () => { ... }, { retry: 3 })
// Both: timeout + retry
it('network test', async () => { ... }, { timeout: 10000, retry: 2 })Works with it(), test(), and it.only().
Verify that the expected number of assertions ran during a test:
it('calls both callbacks', () => {
expect.assertions(2) // exactly 2 assertions must run
expect(a).toBe(1)
expect(b).toBe(2)
})
it('has at least one assertion', () => {
expect.hasAssertions() // at least 1 assertion must run
expect(result).toBeTruthy()
})By default, tests run sequentially. Use describe.concurrent when tests are independent:
describe.concurrent('crypto operations', () => {
it('hash', async () => { /* 100ms */ }) // all three run
it('sign', async () => { /* 100ms */ }) // at the same time
it('verify', async () => { /* 100ms */ }) // total: ~100ms, not 300ms
})Nested suites within a concurrent suite still run sequentially for predictability.
Use .only to run a single test or suite during debugging. All other tests are skipped:
describe('math', () => {
it.only('this one runs', () => {
expect(1 + 1).toBe(2)
})
it('this is skipped', () => {
expect(true).toBeTruthy()
})
})describe.only focuses an entire suite — all tests inside it run:
describe.only('focused suite', () => {
it('runs', () => { /* ... */ })
it('also runs', () => { /* ... */ })
})
describe('skipped suite', () => {
it('does not run', () => { /* ... */ })
})Multiple .only markers can coexist. Remember to remove them before committing.
Document planned tests without failing the suite:
describe('auth', () => {
it.todo('should handle token refresh')
it.todo('should revoke expired sessions')
it('logs in', () => {
// this test runs normally
})
})Todo tests appear in output with a todo label and are counted separately.
Reduce duplication for data-driven tests:
// Scalar values
it.each([1, 2, 3])('doubles %d', (n) => {
expect(n * 2).toBeGreaterThan(n)
})
// Tuple values (array elements are spread as arguments)
it.each([
[1, 2, 3],
[2, 3, 5],
[10, 20, 30],
])('%d + %d = %d', (a, b, expected) => {
expect(a + b).toBe(expected)
})
// Object values with $property interpolation
it.each([
{ input: 'hello', len: 5 },
{ input: 'hi', len: 2 },
])('$input has length $len', ({ input, len }) => {
expect(input).toHaveLength(len)
})Name template specifiers:
| Specifier | Description |
|---|---|
%s |
String |
%d, %f |
Number |
%i |
Integer (floored) |
%j, %o |
JSON |
%# |
Test case index |
$property |
Object property value |
expect(value).toBe(expected) // strict ===
expect(value).toEqual(expected) // deep structural equality
expect(value).toBeTruthy()
expect(value).toBeFalsy()
expect(value).toBeNull()
expect(value).toBeUndefined()
expect(value).toBeDefined()
expect(value).toBeNaN() // Number.isNaN
expect(value).toBeInstanceOf(Class)
expect(value).toBeTypeOf('string') // typeof check (Vitest-compatible)
expect(value).toSatisfy(fn) // custom predicate
expect(value).toBeGreaterThan(n)
expect(value).toBeLessThan(n)
expect(value).toBeGreaterThanOrEqual(n)
expect(value).toBeLessThanOrEqual(n)
expect(value).toBeCloseTo(n, digits?) // float comparison (default: 2 digits)
expect(value).toContain(item) // string or array (===)
expect(value).toContainEqual(item) // array (deep equality)
expect(value).toMatch(/regex/) // regex or string pattern
expect(value).toHaveLength(n)
expect(value).toMatchObject(subset) // partial deep match
expect(value).toHaveProperty('a.b', v) // nested property exists, optional value
expect(value).toStrictEqual(expected) // deep equality + undefined props + constructors
expect(fn).toThrow()
expect(fn).toThrow('message')
expect(fn).toThrow(/pattern/)
expect(spy).toHaveBeenCalled()
expect(spy).toHaveBeenCalledTimes(n)
expect(spy).toHaveBeenCalledWith(a, b)
expect(spy).toHaveBeenLastCalledWith(a) // last call args
expect(spy).toHaveBeenNthCalledWith(n, a) // nth call args (1-indexed)
expect(spy).toHaveReturned() // returned at least once
expect(spy).toHaveReturnedTimes(n) // returned exactly n times
expect(spy).toHaveReturnedWith(value) // any return matches
expect(spy).toHaveLastReturnedWith(value)
expect(spy).toHaveNthReturnedWith(n, value)Use inside toEqual, toMatchObject, toHaveBeenCalledWith, and other deep comparisons:
expect(42).toEqual(expect.any(Number))
expect('hello').toEqual(expect.any(String))
expect({ id: 1 }).toEqual(expect.anything())
expect('hello world').toEqual(expect.stringContaining('world'))
expect('abc-123').toEqual(expect.stringMatching(/\d+/))
expect({ a: 1, b: 2 }).toEqual(expect.objectContaining({ a: 1 }))
expect([1, 2, 3]).toEqual(expect.arrayContaining([3, 1]))
expect({ price: 0.1 + 0.2 }).toEqual({ price: expect.closeTo(0.3) })Negated asymmetric matchers:
expect([1, 2]).toEqual(expect.not.arrayContaining([3, 4]))
expect({ a: 1 }).toEqual(expect.not.objectContaining({ b: 2 }))
expect('hello').toEqual(expect.not.stringContaining('xyz'))
expect('hello').toEqual(expect.not.stringMatching(/\d+/))Composable: nest matchers freely:
expect(spy).toHaveBeenCalledWith(
expect.any(Number),
expect.objectContaining({ name: expect.any(String) })
)All assertions support .not, .resolves, and .rejects:
expect(1).not.toBe(2)
expect([1, 2]).not.toContain(3)
expect(() => {}).not.toThrow()
await expect(Promise.resolve(42)).resolves.toBe(42)
await expect(Promise.reject(new Error('fail'))).rejects.toBeInstanceOf(Error)Create standalone spy functions or spy on existing methods.
Create a standalone spy function. Optionally wrap an implementation.
import { spyFn } from '@igorjs/pure-test'
const callback = spyFn()
callback(1, 2)
callback('a')
callback.mock.calls // [[1, 2], ['a']]
callback.mock.lastCall // ['a']
callback.mock.calls.length // 2
callback.mock.results // [{ type: 'return', value: undefined }, ...]
// With initial implementation
const double = spyFn((x) => x * 2)
double(5) // 10Spy on an existing method. Calls through to the original by default.
import { spyOn, restoreAllMocks } from '@igorjs/pure-test'
const obj = { greet: (name) => `hello ${name}` }
const spy = spyOn(obj, 'greet')
obj.greet('world') // 'hello world' (calls original)
spy.mock.calls // [['world']]
spy.mockReturnValue('hi')
obj.greet('world') // 'hi' (overridden)
spy.mockRestore() // restore original
obj.greet('world') // 'hello world'All spy methods are chainable and Vitest-compatible:
const spy = spyFn()
// Fixed return value
spy.mockReturnValue(42)
spy() // 42
// One-time return values (chainable, uses queue)
spy.mockReturnValueOnce(1).mockReturnValueOnce(2).mockReturnValueOnce(3)
spy() // 1
spy() // 2
spy() // 3
spy() // falls through to mockReturnValue or undefined
// Custom implementation
spy.mockImplementation((a, b) => a + b)
spy(2, 3) // 5
// One-time implementation
spy.mockImplementationOnce(() => 'once')
spy() // 'once'
spy() // falls through to mockImplementation
// Async: resolved promise
spy.mockResolvedValue({ data: 'ok' })
await spy() // { data: 'ok' }
// Async: one-time resolved
spy.mockResolvedValueOnce('first').mockResolvedValueOnce('second')
await spy() // 'first'
await spy() // 'second'
// Async: rejected promise
spy.mockRejectedValue(new Error('fail'))
await spy() // throws Error('fail')
// Async: one-time rejected
spy.mockRejectedValueOnce(new Error('once'))
// Throw synchronously
spy.mockThrow(new Error('boom'))
spy() // throws Error('boom')
// One-time throw
spy.mockThrowOnce(new Error('once'))
// Return `this` context
spy.mockReturnThis()
const obj = { method: spy }
obj.method() === obj // truespy.mock.calls // Parameters<T>[] — all call arguments
spy.mock.lastCall // Parameters<T> | undefined — last call args
spy.mock.results // { type: 'return'|'throw', value }[] — all results
spy.mock.invocationCallOrder // number[] — global call ordering
spy.getMockName() // string — spy's name
spy.mockName('myFn') // set name (for assertion messages)
spy.getMockImplementation() // T | undefined — current implementationspy.mockClear() // clear call history, keep implementation
spy.mockReset() // clear history + reset implementation to default
spy.mockRestore() // clear history + restore original (spyOn only)Shallow mock: replace all function properties with spies.
import { mock, restoreAllMocks } from '@igorjs/pure-test'
const api = {
getUser: (id) => fetch(`/users/${id}`),
deleteUser: (id) => fetch(`/users/${id}`, { method: 'DELETE' }),
baseUrl: 'https://api.example.com',
}
mock(api)
api.getUser(1)
api.getUser.mock.calls // [[1]]
api.baseUrl // 'https://api.example.com' (non-functions untouched)
restoreAllMocks() // restore originalsRecursively mock all methods on nested objects.
import { mockDeep, restoreAllMocks } from '@igorjs/pure-test'
const db = {
users: {
find: (id) => ({ id, name: 'Alice' }),
create: (data) => ({ id: 1, ...data }),
},
posts: {
list: () => [],
},
}
mockDeep(db)
db.users.find.mockReturnValue({ id: 1, name: 'Mock' })
db.users.find(1) // { id: 1, name: 'Mock' }
db.users.find.mock.calls // [[1]]
db.posts.list.mock.calls // []
restoreAllMocks() // restore all originals at every levelHandles circular references safely.
For easy migration, Pure Test exports vi and jest namespace objects that map to the built-in spy/mock functions:
// Vitest migration
import { vi } from '@igorjs/pure-test' // was: import { vi } from 'vitest'
const spy = vi.fn()
vi.spyOn(obj, 'method')
vi.restoreAllMocks()
vi.clearAllMocks()
vi.resetAllMocks()
// Jest migration
import { jest } from '@igorjs/pure-test' // was: import { jest } from '@jest/globals'
const spy = jest.fn()
jest.spyOn(obj, 'method')
jest.restoreAllMocks()These cover the spy/mock and fake timer subsets. Features like vi.mock() and jest.mock() are intentionally not included (see What Pure Test will never support).
Control time in your tests. Replaces setTimeout, setInterval, Date, and performance.now() with controllable fakes.
import { useFakeTimers, advanceTimersByTime, restoreAllMocks } from '@igorjs/pure-test'
describe('debounce', () => {
afterEach(() => restoreAllMocks()) // also restores real timers
it('fires after delay', async () => {
useFakeTimers()
let fired = false
setTimeout(() => { fired = true }, 500)
await advanceTimersByTime(499)
expect(fired).toBe(false)
await advanceTimersByTime(1)
expect(fired).toBe(true)
})
})Also available via vi.useFakeTimers() / jest.useFakeTimers().
useFakeTimers({
now: new Date('2025-01-01'), // initial fake time (default: real Date.now())
toFake: ['setTimeout', 'Date'], // selective faking (default: all)
loopLimit: 10_000, // max iterations for runAllTimers (default: 10000)
})Timer set/clear pairs are atomic: faking setTimeout also fakes clearTimeout.
await advanceTimersByTime(1000) // advance clock, fire due callbacks
await runAllTimers() // drain queue (throws on infinite loop)
await runOnlyPendingTimers() // fire current queue, skip newly scheduled
getTimerCount() // number of pending timersAll advancement functions are async and await each callback, so async timer callbacks complete before the function returns.
useFakeTimers({ now: new Date('2025-01-01') })
Date.now() // 1735689600000
new Date() // 2025-01-01T00:00:00.000Z
new Date('2020-06-15') // passes through to real Date
setSystemTime(new Date('2025-06-01')) // change clock, don't fire timers
getRealSystemTime() // real Date.now(), bypasses fakenew Date() with no arguments returns fake time. new Date(value) passes through. instanceof Date works correctly.
Note: Fake timers use module-level state and should not be used with
describe.concurrent.
import { restoreAllMocks, clearAllMocks, resetAllMocks } from '@igorjs/pure-test'
restoreAllMocks() // restore all spied methods to originals
clearAllMocks() // clear call history on all spies, keep implementations
resetAllMocks() // clear history + reset implementations on all spiesFour built-in output formats:
| Reporter | Description | Use case |
|---|---|---|
spec |
Human-readable with suite nesting (default) | Local development |
tap |
TAP format | CI pipelines, piping to TAP consumers |
json |
Machine-readable JSON | Custom tooling, dashboards |
minimal |
Dots (. pass, F fail, s skip) |
Large test suites, quick overview |
pure-test tests/ --reporter spec
pure-test tests/ --reporter tap
pure-test tests/ --reporter json
pure-test tests/ --reporter minimalProgrammatic selection:
import { setReporter } from '@igorjs/pure-test'
setReporter('tap')Custom reporter:
import { setReporter } from '@igorjs/pure-test'
setReporter({
name: 'custom',
format: (summary) => {
const icon = summary.failed > 0 ? 'FAIL' : 'PASS'
return `${icon}: ${summary.passed}/${summary.results.length} passed in ${Math.round(summary.duration)}ms`
}
})Filter tests by name when running directly (without the CLI):
import { setGrep } from '@igorjs/pure-test'
setGrep('auth') // string (treated as regex)
setGrep(/User.*login/i) // RegExpMatches against the full hierarchical test name (describe > test).
Pure Test does not isolate tests. This is intentional.
Worker-per-file isolation (like Jest) costs ~50ms per file. For a project with 100 test files, that's 5 seconds of overhead before any test runs.
Instead, use the tools provided:
describe('database', () => {
let db
beforeEach(() => {
db = createTestDb() // fresh state per test
})
afterEach(() => {
db.close() // cleanup per test
})
it('inserts', () => {
db.insert({ id: 1 })
expect(db.count()).toBe(1)
})
it('starts empty', () => {
expect(db.count()).toBe(0) // not affected by previous test
})
})If your tests have race conditions in sequential mode, that's a bug in the tests. describe.concurrent is opt-in: you explicitly take responsibility for independence.
Already using Jest or Vitest? Step-by-step porting guides with before/after examples:
- Migrating from Jest — replace
jest.fn()withspyFn(), drop jest.config.js - Migrating from Vitest — replace
vi.fn()withspyFn(), drop vitest.config.ts
Mock instance methods (mockReturnValue, mockImplementation, mock.calls, etc.) are API-compatible. Most tests only need an import change.
| Pure Test | Jest | Vitest | |
|---|---|---|---|
| 50 tests (same suite) | ~57ms | ~762ms | ~559ms |
| Startup (1 test) | ~50ms | ~880ms | ~660ms |
| Install footprint | 0 packages | ~194 packages | ~35 packages |
| Config needed | No | Yes | Yes |
| Node.js | Yes | Yes | Yes |
| Deno | Yes | No | Experimental |
| Bun | Yes | Partial | Partial |
| Workers/Browser | Yes | No | No |
| Transforms needed | No | Yes (babel/SWC) | Yes (esbuild/SWC) |
| Mock API | vi + jest + individual |
Jest API | vi namespace |
Benchmarks: Apple M-series, Node 25, identical 50-test suite across all three runners, measured with
performance.now()over 7 runs (median). Jest 30.2, Vitest 4.1.
These features work the same way across all three frameworks. If you're using them in Jest or Vitest, they'll work in Pure Test with minimal changes.
| Feature | Pure Test | Jest | Vitest |
|---|---|---|---|
describe / it / test |
Yes | Yes | Yes |
describe.concurrent |
Yes | No | Yes |
describe.skip / it.skip |
Yes | Yes | Yes |
describe.only / it.only |
Yes | Yes | Yes |
it.todo |
Yes | Yes | Yes |
it.each (parameterised tests) |
Yes | Yes | Yes |
useFakeTimers / useRealTimers |
Yes | Yes | Yes |
advanceTimersByTime / runAllTimers |
Yes | Yes | Yes |
beforeAll / afterAll |
Yes | Yes | Yes |
beforeEach / afterEach |
Yes | Yes | Yes |
expect().toBe() |
Yes | Yes | Yes |
expect().toEqual() |
Yes | Yes | Yes |
expect().toBeTruthy/Falsy() |
Yes | Yes | Yes |
expect().toBeNull/Undefined/Defined() |
Yes | Yes | Yes |
expect().toBeInstanceOf() |
Yes | Yes | Yes |
expect().toBeNaN() |
Yes | Yes | Yes |
expect().toBeTypeOf() |
No | No | Yes |
expect().toSatisfy() |
No | No | Yes |
expect().toBeGreaterThan() and friends |
Yes | Yes | Yes |
expect().toBeCloseTo() |
Yes | Yes | Yes |
expect().toContain() / toContainEqual() |
Yes | Yes | Yes |
expect().toMatch() |
Yes | Yes | Yes |
expect().toHaveLength() |
Yes | Yes | Yes |
expect().toMatchObject() / toHaveProperty() |
Yes | Yes | Yes |
expect().toStrictEqual() |
Yes | Yes | Yes |
expect().toThrow() |
Yes | Yes | Yes |
expect().toHaveBeenCalled/Times/With() |
Yes | Yes | Yes |
expect().toHaveBeenLastCalledWith() |
Yes | Yes | Yes |
expect().toHaveBeenNthCalledWith() |
Yes | Yes | Yes |
expect().toHaveReturned/Times/With() |
Yes | Yes | Yes |
expect().toHaveLastReturnedWith() |
Yes | Yes | Yes |
expect().toHaveNthReturnedWith() |
Yes | Yes | Yes |
expect.any() / asymmetric matchers |
Yes | Yes | Yes |
expect.not.* asymmetric matchers |
Yes | Yes | Yes |
expect.closeTo() |
Yes | Yes | Yes |
expect.assertions() / expect.hasAssertions() |
Yes | Yes | Yes |
.not / .resolves / .rejects modifiers |
Yes | Yes | Yes |
spyOn(obj, 'prop', 'get'|'set') |
Yes | Yes | Yes |
Test timeout it('name', fn, 5000) |
Yes | Yes | Yes |
--grep / -t test name filtering |
Yes (Mocha) | Yes | Yes |
spyFn() / fn() / vi.fn() |
Yes | Yes | Yes |
spyOn() |
Yes | Yes | Yes |
mockReturnValue / mockReturnValueOnce |
Yes | Yes | Yes |
mockImplementation / mockImplementationOnce |
Yes | Yes | Yes |
mockResolvedValue / mockRejectedValue |
Yes | Yes | Yes |
mockThrow / mockThrowOnce |
Yes | No | Yes (v4.1+) |
mockReturnThis |
Yes | Yes | Yes |
mockClear / mockReset / mockRestore |
Yes | Yes | Yes |
mock.calls / mock.results / mock.lastCall |
Yes | Yes | Yes |
mockDeep() |
Yes | Via jest-mock-extended | No |
| Multiple reporters (TAP, JSON, spec, minimal) | Yes | Via packages | Via packages |
| Custom reporters | Yes | Via packages | Via packages |
| Async test support | Yes | Yes | Yes |
These features are intentionally excluded. Each one conflicts with our philosophy of zero dependencies, no transforms, no magic, and cross-runtime compatibility.
| Feature | Jest | Vitest | Why we skip it |
|---|---|---|---|
Module mocking (jest.mock / vi.mock) |
Yes | Yes | Requires transform hooks that intercept import statements at compile time. This is runtime-specific magic: Jest uses babel, Vitest uses Vite. There's no cross-runtime way to do it. Use dependency injection instead: pass dependencies as parameters, mock at the call site. |
Global stubbing (vi.stubGlobal / vi.stubEnv) |
No | Yes | Mutating globals is fragile and leaks between tests. Pass globals as function parameters instead. |
Hoisted mocks (vi.hoisted) |
No | Yes | Only works with Vite's transform pipeline. The concept doesn't exist without a bundler. |
| Snapshot testing | Yes | Yes | Requires file I/O to read/write .snap files, which isn't available in Workers or browsers. Snapshots are also brittle: they pass when they shouldn't (accepting wrong output) and fail when they shouldn't (formatting changes). Write explicit assertions that document what you expect. |
Coverage (--coverage) |
Yes | Yes | Requires V8 or Istanbul instrumentation which is deeply runtime-specific. Use c8 for Node, deno coverage for Deno, or bun test --coverage for Bun. Separating coverage from the test runner is better architecture. |
| Watch mode | Yes | Yes | Requires file system watchers (FSEvents, inotify) which are runtime-specific. Use watchexec or nodemon externally: watchexec -e mjs -- pure-test tests/. Unix philosophy: do one thing well. |
| Browser environments (jsdom, happy-dom) | Yes | Yes | Simulated DOM is a Node-only concept. Pure Test runs in real browsers via Playwright. Test browser code in a real browser, not a simulation. |
| Worker/thread isolation | Yes | Yes | Costs ~50ms per file in process creation overhead. For 100 test files, that's 5 seconds before any test runs. Use beforeEach/afterEach for test isolation. If your tests need process isolation, your tests have a design problem. |
expect.extend() (custom matchers) |
Yes | Yes | Adds API surface and complexity. Write helper functions that call expect() internally. They compose better and are easier to debug. |
| Config files | Yes (jest.config) |
Yes (vitest.config) |
Config parsing adds startup overhead and a new thing to learn. CLI args cover everything. If you need project-specific settings, use npm scripts. |
| Benchmark mode | No | Yes (bench()) |
Benchmarking is a different tool. Use mitata or tinybench. Mixing tests and benchmarks in one runner conflates two concerns. |
See CONTRIBUTING.md for development setup, coding standards, and how to submit changes.
THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.