mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-09-30 10:23:14 +00:00
fix: running tests and in ci (#536)
* fix: running tests and in ci * cr * cr * cr * exit 0 when no tests * cr
This commit is contained in:
parent
576d2553e3
commit
0923c8b47e
10 changed files with 86 additions and 34 deletions
40
.github/workflows/unit-tests.yml
vendored
Normal file
40
.github/workflows/unit-tests.yml
vendored
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
# This workflow will run unit tests for the current project
|
||||
|
||||
name: Unit Tests
|
||||
permissions:
|
||||
contents: read
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
pull_request:
|
||||
workflow_dispatch: # Allows triggering the workflow manually in GitHub UI
|
||||
|
||||
# If another push to the same PR or branch happens while this workflow is still running,
|
||||
# cancel the earlier run in favor of the next run.
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
unit-tests:
|
||||
name: Unit Tests
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
node-version: [18.x, 20.x]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Enable Corepack
|
||||
run: corepack enable
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: "yarn"
|
||||
- name: Install dependencies
|
||||
run: yarn install --immutable --mode=skip-build
|
||||
- name: Build project
|
||||
run: yarn build
|
||||
- name: Run tests
|
||||
run: yarn test
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
"lint:fix": "eslint . --fix",
|
||||
"format": "prettier --write .",
|
||||
"format:check": "prettier --check .",
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"test": "echo \"Error: no test specified\" && exit 0",
|
||||
"dev": "tsx src/index.tsx"
|
||||
},
|
||||
"dependencies": {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ export default {
|
|||
preset: "ts-jest/presets/default-esm",
|
||||
moduleNameMapper: {
|
||||
"^(\\.{1,2}/.*)\\.js$": "$1",
|
||||
"^@open-swe/shared$": "<rootDir>/../../packages/shared/src/index.ts",
|
||||
"^@open-swe/shared/(.*)$": "<rootDir>/../../packages/shared/src/$1",
|
||||
},
|
||||
transform: {
|
||||
"^.+\\.tsx?$": [
|
||||
|
|
@ -15,4 +17,5 @@ export default {
|
|||
setupFiles: ["dotenv/config"],
|
||||
passWithNoTests: true,
|
||||
testTimeout: 20_000,
|
||||
testMatch: ["<rootDir>/src/**/*.test.ts"],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@
|
|||
"lint:fix": "eslint . --fix",
|
||||
"format": "prettier --write .",
|
||||
"format:check": "prettier --check .",
|
||||
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js --testPathPattern=\\.test\\.ts$ --testPathIgnorePatterns=\\.int\\.test\\.ts$",
|
||||
"test:int": "node --experimental-vm-modules node_modules/jest/bin/jest.js --testPathPattern=\\.int\\.test\\.ts$",
|
||||
"test": "NODE_OPTIONS=--experimental-vm-modules yarn run jest --config jest.config.js --testPathIgnorePatterns=int.test.ts",
|
||||
"test:int": "node --experimental-vm-modules node_modules/jest/bin/jest.js --config jest.config.js --testPathPattern=int.test.ts",
|
||||
"test:single": "NODE_OPTIONS=--experimental-vm-modules yarn run jest --config jest.config.js --testTimeout 100000",
|
||||
"eval:single": "NODE_OPTIONS=--experimental-vm-modules yarn run vitest --config ls.vitest.config.ts --run",
|
||||
"postinstall": "turbo build"
|
||||
|
|
|
|||
|
|
@ -31,7 +31,9 @@ describe("withRetry", () => {
|
|||
.fn<() => Promise<string>>()
|
||||
.mockRejectedValue(new Error("always fails"));
|
||||
|
||||
await expect(withRetry(mockFn)).rejects.toThrow("always fails");
|
||||
const result = await withRetry(mockFn);
|
||||
expect(result).toBeInstanceOf(Error);
|
||||
expect((result as Error).message).toBe("always fails");
|
||||
expect(mockFn).toHaveBeenCalledTimes(4); // 1 initial + 3 retries
|
||||
});
|
||||
|
||||
|
|
@ -40,9 +42,9 @@ describe("withRetry", () => {
|
|||
.fn<() => Promise<string>>()
|
||||
.mockRejectedValue(new Error("always fails"));
|
||||
|
||||
await expect(withRetry(mockFn, { retries: 2 })).rejects.toThrow(
|
||||
"always fails",
|
||||
);
|
||||
const result = await withRetry(mockFn, { retries: 2 });
|
||||
expect(result).toBeInstanceOf(Error);
|
||||
expect((result as Error).message).toBe("always fails");
|
||||
expect(mockFn).toHaveBeenCalledTimes(3); // 1 initial + 2 retries
|
||||
});
|
||||
|
||||
|
|
@ -52,11 +54,11 @@ describe("withRetry", () => {
|
|||
.mockRejectedValue(new Error("always fails"));
|
||||
const startTime = Date.now();
|
||||
|
||||
await expect(withRetry(mockFn, { retries: 2, delay: 100 })).rejects.toThrow(
|
||||
"always fails",
|
||||
);
|
||||
const result = await withRetry(mockFn, { retries: 2, delay: 100 });
|
||||
const endTime = Date.now();
|
||||
|
||||
expect(result).toBeInstanceOf(Error);
|
||||
expect((result as Error).message).toBe("always fails");
|
||||
expect(mockFn).toHaveBeenCalledTimes(3);
|
||||
expect(endTime - startTime).toBeGreaterThanOrEqual(200); // 2 delays of 100ms each
|
||||
});
|
||||
|
|
@ -67,11 +69,11 @@ describe("withRetry", () => {
|
|||
.mockRejectedValue(new Error("always fails"));
|
||||
const startTime = Date.now();
|
||||
|
||||
await expect(withRetry(mockFn, { retries: 2 })).rejects.toThrow(
|
||||
"always fails",
|
||||
);
|
||||
const result = await withRetry(mockFn, { retries: 2 });
|
||||
const endTime = Date.now();
|
||||
|
||||
expect(result).toBeInstanceOf(Error);
|
||||
expect((result as Error).message).toBe("always fails");
|
||||
expect(mockFn).toHaveBeenCalledTimes(3);
|
||||
expect(endTime - startTime).toBeLessThan(50); // Should be very fast with no delay
|
||||
});
|
||||
|
|
@ -81,13 +83,13 @@ describe("withRetry", () => {
|
|||
.fn<() => Promise<string>>()
|
||||
.mockRejectedValue("string error");
|
||||
|
||||
await expect(withRetry(mockFn, { retries: 1 })).rejects.toThrow(
|
||||
"string error",
|
||||
);
|
||||
const result = await withRetry(mockFn, { retries: 1 });
|
||||
expect(result).toBeInstanceOf(Error);
|
||||
expect((result as Error).message).toBe("string error");
|
||||
expect(mockFn).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("should throw the last error after all retries", async () => {
|
||||
it("should return the last error after all retries", async () => {
|
||||
const error1 = new Error("first error");
|
||||
const error2 = new Error("second error");
|
||||
const lastError = new Error("last error");
|
||||
|
|
@ -98,9 +100,9 @@ describe("withRetry", () => {
|
|||
.mockRejectedValueOnce(error2)
|
||||
.mockRejectedValue(lastError);
|
||||
|
||||
await expect(withRetry(mockFn, { retries: 2 })).rejects.toThrow(
|
||||
"last error",
|
||||
);
|
||||
const result = await withRetry(mockFn, { retries: 2 });
|
||||
expect(result).toBeInstanceOf(Error);
|
||||
expect((result as Error).message).toBe("last error");
|
||||
expect(mockFn).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
|
|
@ -140,7 +142,9 @@ describe("createRetryWrapper", () => {
|
|||
|
||||
const wrappedFn = createRetryWrapper(originalFn, { retries: 1 });
|
||||
|
||||
await expect(wrappedFn()).rejects.toThrow("always fails");
|
||||
const result = await wrappedFn();
|
||||
expect(result).toBeInstanceOf(Error);
|
||||
expect((result as Error).message).toBe("always fails");
|
||||
expect(originalFn).toHaveBeenCalledTimes(2); // 1 initial + 1 retry
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import {
|
|||
MAX_INTERNAL_TOKENS,
|
||||
} from "../utils/tokens.js";
|
||||
|
||||
describe("calculateConversationHistoryTokenCount", async () => {
|
||||
describe("calculateConversationHistoryTokenCount", () => {
|
||||
it("should return 0 for empty messages array", async () => {
|
||||
const result = calculateConversationHistoryTokenCount([]);
|
||||
expect(result).toBe(0);
|
||||
|
|
@ -124,8 +124,8 @@ describe("calculateConversationHistoryTokenCount", async () => {
|
|||
});
|
||||
|
||||
expect(resultWithoutOption).toBeGreaterThan(resultWithOption);
|
||||
// First two messages should be ~7 tokens
|
||||
expect(resultWithOption).toBe(7);
|
||||
// First two messages should be ~8 tokens
|
||||
expect(resultWithOption).toBe(8);
|
||||
});
|
||||
|
||||
it("should not separate AI messages with tool calls from their tool messages when excluding from end", async () => {
|
||||
|
|
@ -214,7 +214,7 @@ describe("calculateConversationHistoryTokenCount", async () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("getMessagesSinceLastSummary", async () => {
|
||||
describe("getMessagesSinceLastSummary", () => {
|
||||
it("should return all messages when there is no summary message", async () => {
|
||||
const messages = [
|
||||
new HumanMessage({ content: "Message 1" }),
|
||||
|
|
@ -632,7 +632,7 @@ describe("getMessagesSinceLastSummary", async () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("MAX_INTERNAL_TOKENS constant", async () => {
|
||||
describe("MAX_INTERNAL_TOKENS constant", () => {
|
||||
it("should be defined as 60,000", async () => {
|
||||
expect(MAX_INTERNAL_TOKENS).toBe(60_000);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ export function calculateConversationHistoryTokenCount(
|
|||
excludeCountFromEnd?: number;
|
||||
},
|
||||
) {
|
||||
let totalChars = 0;
|
||||
let totalTokens = 0;
|
||||
let messagesToCount = messages;
|
||||
|
||||
if (options?.excludeCountFromEnd && options.excludeCountFromEnd > 0) {
|
||||
|
|
@ -33,25 +33,25 @@ export function calculateConversationHistoryTokenCount(
|
|||
if (isHumanMessage(m) || isToolMessage(m)) {
|
||||
const contentString = getMessageContentString(m.content);
|
||||
// Divide each char by 4 as it's roughly one token per 4 characters.
|
||||
totalChars += contentString.length / 4;
|
||||
totalTokens += Math.ceil(contentString.length / 4);
|
||||
}
|
||||
|
||||
if (isAIMessage(m)) {
|
||||
const usageMetadata = m.usage_metadata;
|
||||
if (usageMetadata) {
|
||||
totalChars += usageMetadata.output_tokens;
|
||||
totalTokens += usageMetadata.total_tokens;
|
||||
} else {
|
||||
const contentString = getMessageContentString(m.content);
|
||||
totalChars += contentString.length / 4;
|
||||
totalTokens += Math.ceil(contentString.length / 4);
|
||||
m.tool_calls?.forEach((tc) => {
|
||||
const nameAndArgs = tc.name + JSON.stringify(tc.args);
|
||||
totalChars += nameAndArgs.length / 4;
|
||||
totalTokens += Math.ceil(nameAndArgs.length / 4);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return totalChars;
|
||||
return totalTokens;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -18,7 +18,8 @@
|
|||
"strictFunctionTypes": false,
|
||||
"outDir": "dist",
|
||||
"types": ["jest", "node"],
|
||||
"resolveJsonModule": true
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true
|
||||
},
|
||||
"include": ["**/*.ts", "**/*.js", "jest.setup.cjs"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@
|
|||
"format": "turbo format",
|
||||
"format:check": "turbo format:check",
|
||||
"lint": "turbo lint",
|
||||
"lint:fix": "turbo lint:fix"
|
||||
"lint:fix": "turbo lint:fix",
|
||||
"test": "turbo test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"turbo": "^2.5.0",
|
||||
|
|
|
|||
|
|
@ -20,6 +20,9 @@
|
|||
},
|
||||
"dev": {
|
||||
"dependsOn": ["^dev"]
|
||||
},
|
||||
"test": {
|
||||
"dependsOn": ["^test"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue