Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
5 changes: 5 additions & 0 deletions .changeset/seven-apricots-watch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"roo-cline": patch
---

ContextProxy fix - constructor should not be async
4 changes: 2 additions & 2 deletions .github/workflows/code-qa.yml
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,9 @@ jobs:
cache: 'npm'
- name: Install dependencies
run: npm run install:all
- name: Create env.integration file
- name: Create .env.local file
working-directory: e2e
run: echo "OPENROUTER_API_KEY=${{ secrets.OPENROUTER_API_KEY }}" > .env.integration
run: echo "OPENROUTER_API_KEY=${{ secrets.OPENROUTER_API_KEY }}" > .env.local
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Rename this file for consistency.

- name: Run integration tests
working-directory: e2e
run: xvfb-run -a npm run ci
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ docs/_site/

# Dotenv
.env
.env.integration
.env.*
!.env.*.sample


#Local lint config
.eslintrc.local.json
Expand Down
File renamed without changes.
16 changes: 11 additions & 5 deletions e2e/VSCODE_INTEGRATION_TESTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ The test runner (`runTest.ts`) is responsible for:

### Environment Setup

1. Create a `.env.integration` file in the root directory with required environment variables:
1. Create a `.env.local` file in the root directory with required environment variables:

```
OPENROUTER_API_KEY=sk-or-v1-...
Expand Down Expand Up @@ -67,7 +67,7 @@ declare global {

## Running Tests

1. Ensure you have the required environment variables set in `.env.integration`
1. Ensure you have the required environment variables set in `.env.local`

2. Run the integration tests:

Expand Down Expand Up @@ -117,8 +117,10 @@ const interval = 1000
2. **State Management**: Reset extension state before/after tests:

```typescript
await globalThis.provider.updateGlobalState("mode", "Ask")
await globalThis.provider.updateGlobalState("alwaysAllowModeSwitch", true)
await globalThis.api.setConfiguration({
mode: "Ask",
alwaysAllowModeSwitch: true,
})
```

3. **Assertions**: Use clear assertions with meaningful messages:
Expand All @@ -141,8 +143,12 @@ try {

```typescript
let startTime = Date.now()

while (Date.now() - startTime < timeout) {
if (condition) break
if (condition) {
break
}

await new Promise((resolve) => setTimeout(resolve, interval))
}
```
Expand Down
9 changes: 5 additions & 4 deletions e2e/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
"version": "0.1.0",
"private": true,
"scripts": {
"build": "cd .. && npm run build",
"compile": "tsc -p tsconfig.json",
"build": "cd .. && npm run compile && npm run build:webview",
"compile": "rm -rf out && tsc -p tsconfig.json",
"lint": "eslint src --ext ts",
"check-types": "tsc --noEmit",
"test": "npm run compile && npx dotenvx run -f .env.integration -- node ./out/runTest.js",
"ci": "npm run build && npm run test"
"test": "npm run compile && npx dotenvx run -f .env.local -- node ./out/runTest.js",
"ci": "npm run build && npm run test",
"clean": "rimraf out"
},
"dependencies": {},
"devDependencies": {
Expand Down
72 changes: 19 additions & 53 deletions e2e/src/suite/index.ts
Original file line number Diff line number Diff line change
@@ -1,80 +1,46 @@
import * as path from "path"
import Mocha from "mocha"
import { glob } from "glob"
import { RooCodeAPI, ClineProvider } from "../../../src/exports/roo-code"
import * as vscode from "vscode"

import { RooCodeAPI } from "../../../src/exports/roo-code"

import { waitUntilReady } from "./utils"

declare global {
var api: RooCodeAPI
var provider: ClineProvider
var extension: vscode.Extension<RooCodeAPI> | undefined
var panel: vscode.WebviewPanel | undefined
var api: RooCodeAPI
}

export async function run(): Promise<void> {
const mocha = new Mocha({
ui: "tdd",
timeout: 600000, // 10 minutes to compensate for time communicating with LLM while running in GHA.
})

export async function run() {
const mocha = new Mocha({ ui: "tdd", timeout: 300_000 })
const testsRoot = path.resolve(__dirname, "..")

try {
// Find all test files.
const files = await glob("**/**.test.js", { cwd: testsRoot })

// Add files to the test suite.
files.forEach((f: string) => mocha.addFile(path.resolve(testsRoot, f)))

// Set up global extension, api, provider, and panel.
globalThis.extension = vscode.extensions.getExtension("RooVeterinaryInc.roo-cline")
const extension = vscode.extensions.getExtension<RooCodeAPI>("RooVeterinaryInc.roo-cline")

if (!globalThis.extension) {
if (!extension) {
throw new Error("Extension not found")
}

globalThis.api = globalThis.extension.isActive
? globalThis.extension.exports
: await globalThis.extension.activate()

globalThis.provider = globalThis.api.sidebarProvider
const api = extension.isActive ? extension.exports : await extension.activate()

await globalThis.provider.updateGlobalState("apiProvider", "openrouter")
await globalThis.provider.updateGlobalState("openRouterModelId", "anthropic/claude-3.5-sonnet")

await globalThis.provider.storeSecret(
"openRouterApiKey",
process.env.OPENROUTER_API_KEY || "sk-or-v1-fake-api-key",
)

globalThis.panel = vscode.window.createWebviewPanel(
"roo-cline.SidebarProvider",
"Roo Code",
vscode.ViewColumn.One,
{
enableScripts: true,
enableCommandUris: true,
retainContextWhenHidden: true,
localResourceRoots: [globalThis.extension?.extensionUri],
},
)

await globalThis.provider.resolveWebviewView(globalThis.panel)

let startTime = Date.now()
const timeout = 60000
const interval = 1000
await api.setConfiguration({
apiProvider: "openrouter",
openRouterApiKey: process.env.OPENROUTER_API_KEY!,
openRouterModelId: "anthropic/claude-3.5-sonnet",
})

while (Date.now() - startTime < timeout) {
if (globalThis.provider.viewLaunched) {
break
}
await waitUntilReady(api)

await new Promise((resolve) => setTimeout(resolve, interval))
}
globalThis.api = api
globalThis.extension = extension

// Run the mocha test.
return new Promise((resolve, reject) => {
return new Promise<void>((resolve, reject) => {
try {
mocha.run((failures: number) => {
if (failures > 0) {
Expand Down
94 changes: 26 additions & 68 deletions e2e/src/suite/modes.test.ts
Original file line number Diff line number Diff line change
@@ -1,102 +1,60 @@
import * as assert from "assert"

import { waitForMessage } from "./utils"

suite("Roo Code Modes", () => {
test("Should handle switching modes correctly", async function () {
const timeout = 30000
const interval = 1000
const timeout = 300_000
const api = globalThis.api

const testPrompt =
"For each mode (Code, Architect, Ask) respond with the mode name and what it specializes in after switching to that mode, do not start with the current mode, be sure to say 'I AM DONE' after the task is complete"

if (!globalThis.extension) {
assert.fail("Extension not found")
}

let startTime = Date.now()

// Ensure the webview is launched.
while (Date.now() - startTime < timeout) {
if (globalThis.provider.viewLaunched) {
break
}

await new Promise((resolve) => setTimeout(resolve, interval))
}

await globalThis.provider.updateGlobalState("mode", "Ask")
await globalThis.provider.updateGlobalState("alwaysAllowModeSwitch", true)
await globalThis.provider.updateGlobalState("autoApprovalEnabled", true)

// Start a new task.
await globalThis.api.startNewTask(testPrompt)
"For each mode (Code, Architect, Ask) respond with the mode name and what it specializes in after switching to that mode, do not start with the current mode, be sure to say 'I AM DONE' after the task is complete."

// Wait for task to appear in history with tokens.
startTime = Date.now()
await api.setConfiguration({ mode: "Code", alwaysAllowModeSwitch: true, autoApprovalEnabled: true })
await api.startNewTask(testPrompt)

while (Date.now() - startTime < timeout) {
const messages = globalThis.provider.messages
await waitForMessage(api, { include: "I AM DONE", exclude: "be sure to say", timeout })

if (
messages.some(
({ type, text }) =>
type === "say" && text?.includes("I AM DONE") && !text?.includes("be sure to say"),
)
) {
break
}

await new Promise((resolve) => setTimeout(resolve, interval))
}

if (globalThis.provider.messages.length === 0) {
if (api.getMessages().length === 0) {
assert.fail("No messages received")
}

// Log the messages to the console.
globalThis.provider.messages.forEach(({ type, text }) => {
api.getMessages().forEach(({ type, text }) => {
if (type === "say") {
console.log(text)
}
})

// Start Grading Portion of test to grade the response from 1 to 10.
await globalThis.provider.updateGlobalState("mode", "Ask")
let output = globalThis.provider.messages.map(({ type, text }) => (type === "say" ? text : "")).join("\n")

await globalThis.api.startNewTask(
`Given this prompt: ${testPrompt} grade the response from 1 to 10 in the format of "Grade: (1-10)": ${output} \n Be sure to say 'I AM DONE GRADING' after the task is complete`,
)

startTime = Date.now()
await api.setConfiguration({ mode: "Ask" })

while (Date.now() - startTime < timeout) {
const messages = globalThis.provider.messages
let output = api
.getMessages()
.map(({ type, text }) => (type === "say" ? text : ""))
.join("\n")

if (
messages.some(
({ type, text }) =>
type === "say" && text?.includes("I AM DONE GRADING") && !text?.includes("be sure to say"),
)
) {
break
}
await api.startNewTask(
`Given this prompt: ${testPrompt} grade the response from 1 to 10 in the format of "Grade: (1-10)": ${output}\nBe sure to say 'I AM DONE GRADING' after the task is complete.`,
)

await new Promise((resolve) => setTimeout(resolve, interval))
}
await waitForMessage(api, { include: "I AM DONE GRADING", exclude: "be sure to say", timeout })

if (globalThis.provider.messages.length === 0) {
if (api.getMessages().length === 0) {
assert.fail("No messages received")
}

globalThis.provider.messages.forEach(({ type, text }) => {
api.getMessages().forEach(({ type, text }) => {
if (type === "say" && text?.includes("Grade:")) {
console.log(text)
}
})

const gradeMessage = globalThis.provider.messages.find(
({ type, text }) => type === "say" && !text?.includes("Grade: (1-10)") && text?.includes("Grade:"),
)?.text
const gradeMessage = api
.getMessages()
.find(
({ type, text }) => type === "say" && !text?.includes("Grade: (1-10)") && text?.includes("Grade:"),
)?.text

const gradeMatch = gradeMessage?.match(/Grade: (\d+)/)
const gradeNum = gradeMatch ? parseInt(gradeMatch[1]) : undefined
Expand Down
58 changes: 58 additions & 0 deletions e2e/src/suite/subtasks.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import * as assert from "assert"

import { sleep, waitForToolUse, waitForMessage } from "./utils"

suite("Roo Code Subtasks", () => {
test.skip("Should handle subtask cancellation and resumption correctly", async function () {
const api = globalThis.api

await api.setConfiguration({
mode: "Code",
alwaysAllowModeSwitch: true,
alwaysAllowSubtasks: true,
autoApprovalEnabled: true,
})

// Start a parent task that will create a subtask.
await api.startNewTask(
"You are the parent task. " +
"Create a subtask by using the new_task tool with the message 'You are the subtask'. " +
"After creating the subtask, wait for it to complete and then respond with 'Parent task resumed'.",
)

await waitForToolUse(api, "new_task")

// Cancel the current task (which should be the subtask).
await api.cancelTask()

// Check if the parent task is still waiting (not resumed). We need to
// wait a bit to ensure any task resumption would have happened.
await sleep(5_000)

// The parent task should not have resumed yet, so we shouldn't see
// "Parent task resumed".
assert.ok(
!api.getMessages().some(({ type, text }) => type === "say" && text?.includes("Parent task resumed")),
"Parent task should not have resumed after subtask cancellation.",
)

// Start a new task with the same message as the subtask.
await api.startNewTask("You are the subtask")

// Wait for the subtask to complete.
await waitForMessage(api, { include: "Task complete" })

// Verify that the parent task is still not resumed. We need to wait a
// bit to ensure any task resumption would have happened.
await sleep(5_000)

// The parent task should still not have resumed.
assert.ok(
!api.getMessages().some(({ type, text }) => type === "say" && text?.includes("Parent task resumed")),
"Parent task should not have resumed after subtask completion.",
)

// Clean up - cancel all tasks.
await api.cancelTask()
})
})
Loading