@bniladridas/cursor 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.clang-tidy +28 -0
- package/.dockerignore +56 -0
- package/.env.example +29 -0
- package/.github/CODEOWNERS +2 -0
- package/.github/ISSUE_TEMPLATE/blank.md +27 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +33 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +24 -0
- package/.github/SECURITY.md +24 -0
- package/.github/codeql/codeql-config.yml +8 -0
- package/.github/dependabot.yml +14 -0
- package/.github/labeler.yml +50 -0
- package/.github/packaging/brand-cursor.png +0 -0
- package/.github/packaging/database/init.sql +48 -0
- package/.github/packaging/docker/Dockerfile +111 -0
- package/.github/packaging/docker/docker-compose.yml +56 -0
- package/.github/packaging/scripts/preflight.sh +413 -0
- package/.github/packaging/scripts/prepare-release.sh +141 -0
- package/.github/packaging/scripts/release.sh +22 -0
- package/.github/packaging/scripts/setup-git-hooks.sh +73 -0
- package/.github/pull_request_template.md +31 -0
- package/.github/signed.json +9 -0
- package/.github/workflows/README.md +23 -0
- package/.github/workflows/ci.yml +181 -0
- package/.github/workflows/cla.yml +33 -0
- package/.github/workflows/formula-sha.yml +63 -0
- package/.github/workflows/issue-response.yml +44 -0
- package/.github/workflows/labeler.yml +42 -0
- package/.github/workflows/pr-body.yml +49 -0
- package/.github/workflows/release.yml +176 -0
- package/.github/workflows/security.yml +94 -0
- package/.github/workflows/stale.yml +38 -0
- package/AGENTS.md +49 -0
- package/CHANGELOG.md +3 -0
- package/CMakeLists.txt +646 -0
- package/Formula/cursor.rb +46 -0
- package/LICENSE +201 -0
- package/Makefile +28 -0
- package/README.md +46 -0
- package/cli.js +16 -0
- package/include/agent.h +86 -0
- package/include/agent_mode.h +17 -0
- package/include/memory_manager.h +102 -0
- package/include/services/ai_service.h +31 -0
- package/include/services/auth_service.h +87 -0
- package/include/services/checkpoint_service.h +69 -0
- package/include/services/codebase_service.h +38 -0
- package/include/services/command_service.h +23 -0
- package/include/services/context_service.h +74 -0
- package/include/services/database_service.h +56 -0
- package/include/services/error_service.h +106 -0
- package/include/services/file_service.h +51 -0
- package/include/services/git_service.h +29 -0
- package/include/services/github_service.h +85 -0
- package/include/services/mcp_service.h +85 -0
- package/include/services/multi_file_service.h +93 -0
- package/include/services/sandbox_service.h +96 -0
- package/include/services/theme_service.h +67 -0
- package/include/services/web_service.h +52 -0
- package/include/utils/config.h +68 -0
- package/include/utils/memory_utils.h +79 -0
- package/include/utils/platform.h +56 -0
- package/include/utils/ui.h +43 -0
- package/include/utils/validation.h +63 -0
- package/include/utils/version.h.in +17 -0
- package/install.js +49 -0
- package/package.json +16 -0
- package/release/checksums.txt +3 -0
- package/release/cursor-linux/cursor_v0.1.7_linux_amd64.tar.gz +0 -0
- package/release/cursor-macos/cursor_v0.1.7_darwin_arm64.tar.gz +0 -0
- package/release/cursor-windows/cursor__windows_amd64.zip +0 -0
- package/src/agent.cpp +2026 -0
- package/src/main.cpp +97 -0
- package/src/memory_manager.cpp +814 -0
- package/src/services/ai_service.cpp +366 -0
- package/src/services/auth_service.cpp +779 -0
- package/src/services/checkpoint_service.cpp +465 -0
- package/src/services/codebase_service.cpp +233 -0
- package/src/services/command_service.cpp +82 -0
- package/src/services/context_service.cpp +348 -0
- package/src/services/database_service.cpp +148 -0
- package/src/services/error_service.cpp +438 -0
- package/src/services/file_service.cpp +349 -0
- package/src/services/git_service.cpp +148 -0
- package/src/services/github_service.cpp +435 -0
- package/src/services/mcp_service.cpp +481 -0
- package/src/services/multi_file_service.cpp +591 -0
- package/src/services/sandbox_service.cpp +678 -0
- package/src/services/theme_service.cpp +429 -0
- package/src/services/web_service.cpp +532 -0
- package/src/utils/config.cpp +77 -0
- package/src/utils/memory_utils.cpp +93 -0
- package/src/utils/ui.cpp +307 -0
- package/src/utils/validation.cpp +306 -0
- package/src/utils/version.cpp +175 -0
- package/tests/e2e/docker-compose.yml +195 -0
- package/tests/e2e/run_e2e_tests.sh +70 -0
- package/tests/e2e/run_tests_in_docker.sh +115 -0
- package/tests/main_test.cpp +16 -0
- package/tests/mocks/mock_ollama.py +98 -0
- package/tests/mocks/start_nginx.sh +64 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
#include "version.h"
|
|
2
|
+
#include <filesystem>
|
|
3
|
+
#include <iostream>
|
|
4
|
+
#include <string>
|
|
5
|
+
|
|
6
|
+
#include <curl/curl.h>
|
|
7
|
+
|
|
8
|
+
#ifdef __APPLE__
|
|
9
|
+
#include <mach-o/dyld.h>
|
|
10
|
+
#endif
|
|
11
|
+
|
|
12
|
+
namespace {
|
|
13
|
+
|
|
14
|
+
size_t write_to_string(void *ptr, size_t size, size_t nmemb, void *userdata) {
|
|
15
|
+
auto *str = static_cast<std::string *>(userdata);
|
|
16
|
+
str->append(static_cast<char *>(ptr), size * nmemb);
|
|
17
|
+
return size * nmemb;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
size_t write_to_file(void *ptr, size_t size, size_t nmemb, void *userdata) {
|
|
21
|
+
auto *file = static_cast<FILE *>(userdata);
|
|
22
|
+
return fwrite(ptr, size, nmemb, file);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
std::string get_platform_binary_name() {
|
|
26
|
+
#ifdef _WIN32
|
|
27
|
+
return "cursor-windows.exe";
|
|
28
|
+
#elif defined(__APPLE__)
|
|
29
|
+
return "cursor-macos";
|
|
30
|
+
#else
|
|
31
|
+
return "cursor-linux";
|
|
32
|
+
#endif
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
std::string get_exe_path() {
|
|
36
|
+
try {
|
|
37
|
+
return std::filesystem::canonical("/proc/self/exc").string();
|
|
38
|
+
} catch (...) {
|
|
39
|
+
}
|
|
40
|
+
#ifdef __APPLE__
|
|
41
|
+
char buf[1024];
|
|
42
|
+
uint32_t size = sizeof(buf);
|
|
43
|
+
if (_NSGetExecutablePath(buf, &size) == 0) {
|
|
44
|
+
return std::filesystem::canonical(buf).string();
|
|
45
|
+
}
|
|
46
|
+
#endif
|
|
47
|
+
return {};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
std::string fetch_latest_version() {
|
|
51
|
+
CURL *curl = curl_easy_init();
|
|
52
|
+
if (!curl)
|
|
53
|
+
return {};
|
|
54
|
+
|
|
55
|
+
std::string response;
|
|
56
|
+
curl_easy_setopt(curl, CURLOPT_URL,
|
|
57
|
+
"https://api.github.com/repos/bniladridas/cursor/releases/"
|
|
58
|
+
"latest");
|
|
59
|
+
curl_easy_setopt(curl, CURLOPT_USERAGENT, "cursor-agent");
|
|
60
|
+
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_to_string);
|
|
61
|
+
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
|
|
62
|
+
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
|
63
|
+
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 5L);
|
|
64
|
+
|
|
65
|
+
CURLcode res = curl_easy_perform(curl);
|
|
66
|
+
curl_easy_cleanup(curl);
|
|
67
|
+
|
|
68
|
+
if (res != CURLE_OK)
|
|
69
|
+
return {};
|
|
70
|
+
|
|
71
|
+
auto tag_pos = response.find("\"tag_name\":\"");
|
|
72
|
+
if (tag_pos == std::string::npos)
|
|
73
|
+
return {};
|
|
74
|
+
tag_pos += 13;
|
|
75
|
+
auto tag_end = response.find("\"", tag_pos);
|
|
76
|
+
return response.substr(tag_pos, tag_end - tag_pos);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
} // namespace
|
|
80
|
+
|
|
81
|
+
namespace Version {
|
|
82
|
+
|
|
83
|
+
const char *get_version() { return cursor_version_string; }
|
|
84
|
+
|
|
85
|
+
const char *get_build_info() {
|
|
86
|
+
static std::string build_info;
|
|
87
|
+
if (build_info.empty()) {
|
|
88
|
+
build_info = std::string(__DATE__) + " " + __TIME__;
|
|
89
|
+
}
|
|
90
|
+
return build_info.c_str();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
void print_version_info() {
|
|
94
|
+
std::cout << "Cursor v" << get_version() << "\n";
|
|
95
|
+
std::cout << "Built: " << get_build_info() << "\n";
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
std::string check_update() {
|
|
99
|
+
std::string latest = fetch_latest_version();
|
|
100
|
+
if (latest.empty() || latest == get_version())
|
|
101
|
+
return {};
|
|
102
|
+
return latest;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
bool download_and_install(const std::string &version) {
|
|
106
|
+
std::string binary_name = get_platform_binary_name();
|
|
107
|
+
std::string url =
|
|
108
|
+
"https://github.com/bniladridas/cursor/releases/download/" + version +
|
|
109
|
+
"/" + binary_name;
|
|
110
|
+
|
|
111
|
+
std::string tmp = "/tmp/cursor-update-" + version;
|
|
112
|
+
#ifdef _WIN32
|
|
113
|
+
tmp += ".exe";
|
|
114
|
+
#endif
|
|
115
|
+
|
|
116
|
+
std::cout << "Downloading v" << version << "...\n";
|
|
117
|
+
|
|
118
|
+
FILE *fp = fopen(tmp.c_str(), "wb");
|
|
119
|
+
if (!fp) {
|
|
120
|
+
std::cerr << "Failed to create temp file\n";
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
CURL *curl = curl_easy_init();
|
|
125
|
+
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
|
126
|
+
curl_easy_setopt(curl, CURLOPT_USERAGENT, "cursor-agent");
|
|
127
|
+
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_to_file);
|
|
128
|
+
curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
|
|
129
|
+
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
|
130
|
+
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 300L);
|
|
131
|
+
|
|
132
|
+
CURLcode res = curl_easy_perform(curl);
|
|
133
|
+
curl_easy_cleanup(curl);
|
|
134
|
+
fclose(fp);
|
|
135
|
+
|
|
136
|
+
if (res != CURLE_OK) {
|
|
137
|
+
std::cerr << "Download failed: " << curl_easy_strerror(res) << "\n";
|
|
138
|
+
std::filesystem::remove(tmp);
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
std::filesystem::permissions(tmp,
|
|
143
|
+
std::filesystem::perms::owner_exec |
|
|
144
|
+
std::filesystem::perms::owner_read |
|
|
145
|
+
std::filesystem::perms::owner_write,
|
|
146
|
+
std::filesystem::perm_options::add);
|
|
147
|
+
|
|
148
|
+
std::string exe = get_exe_path();
|
|
149
|
+
if (exe.empty()) {
|
|
150
|
+
std::cerr << "Downloaded to: " << tmp << "\nManually replace binary.\n";
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
std::string backup = exe + ".bak";
|
|
155
|
+
std::error_code ec;
|
|
156
|
+
std::filesystem::rename(exe, backup, ec);
|
|
157
|
+
if (ec) {
|
|
158
|
+
std::cerr << "Failed to backup: " << ec.message() << "\n";
|
|
159
|
+
std::filesystem::remove(tmp);
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
std::filesystem::rename(tmp, exe, ec);
|
|
164
|
+
if (ec) {
|
|
165
|
+
std::cerr << "Failed to install: " << ec.message() << "\n";
|
|
166
|
+
std::filesystem::rename(backup, exe);
|
|
167
|
+
std::filesystem::remove(tmp);
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
std::cout << "Updated to v" << version << " successfully!\n";
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
} // namespace Version
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
networks:
|
|
2
|
+
e2e-network:
|
|
3
|
+
driver: bridge
|
|
4
|
+
|
|
5
|
+
services:
|
|
6
|
+
mock-together:
|
|
7
|
+
image: nginx:alpine
|
|
8
|
+
entrypoint: ["/bin/sh", "/start_nginx.sh"]
|
|
9
|
+
healthcheck:
|
|
10
|
+
test: ["CMD", "wget", "-qO-", "--spider", "http://127.0.0.1/health"]
|
|
11
|
+
interval: 10s
|
|
12
|
+
timeout: 5s
|
|
13
|
+
retries: 3
|
|
14
|
+
start_period: 10s
|
|
15
|
+
container_name: mock-together
|
|
16
|
+
environment:
|
|
17
|
+
- MOCK_TYPE=together
|
|
18
|
+
ports:
|
|
19
|
+
- "8081:80"
|
|
20
|
+
volumes:
|
|
21
|
+
- ./tests/mocks/start_nginx.sh:/start_nginx.sh:ro
|
|
22
|
+
networks:
|
|
23
|
+
- e2e-network
|
|
24
|
+
|
|
25
|
+
mock-cerebras:
|
|
26
|
+
image: nginx:alpine
|
|
27
|
+
entrypoint: ["/bin/sh", "/start_nginx.sh"]
|
|
28
|
+
healthcheck:
|
|
29
|
+
test: ["CMD", "wget", "-qO-", "--spider", "http://127.0.0.1/health"]
|
|
30
|
+
interval: 10s
|
|
31
|
+
timeout: 5s
|
|
32
|
+
retries: 3
|
|
33
|
+
start_period: 10s
|
|
34
|
+
container_name: mock-cerebras
|
|
35
|
+
environment:
|
|
36
|
+
- MOCK_TYPE=cerebras
|
|
37
|
+
ports:
|
|
38
|
+
- "8082:80"
|
|
39
|
+
volumes:
|
|
40
|
+
- ./tests/mocks/start_nginx.sh:/start_nginx.sh:ro
|
|
41
|
+
networks:
|
|
42
|
+
- e2e-network
|
|
43
|
+
|
|
44
|
+
mock-fireworks:
|
|
45
|
+
image: nginx:alpine
|
|
46
|
+
entrypoint: ["/bin/sh", "/start_nginx.sh"]
|
|
47
|
+
healthcheck:
|
|
48
|
+
test: ["CMD", "wget", "-qO-", "--spider", "http://127.0.0.1/health"]
|
|
49
|
+
interval: 10s
|
|
50
|
+
timeout: 5s
|
|
51
|
+
retries: 3
|
|
52
|
+
start_period: 10s
|
|
53
|
+
container_name: mock-fireworks
|
|
54
|
+
environment:
|
|
55
|
+
- MOCK_TYPE=fireworks
|
|
56
|
+
ports:
|
|
57
|
+
- "8083:80"
|
|
58
|
+
volumes:
|
|
59
|
+
- ./tests/mocks/start_nginx.sh:/start_nginx.sh:ro
|
|
60
|
+
networks:
|
|
61
|
+
- e2e-network
|
|
62
|
+
|
|
63
|
+
mock-groq:
|
|
64
|
+
image: nginx:alpine
|
|
65
|
+
entrypoint: ["/bin/sh", "/start_nginx.sh"]
|
|
66
|
+
healthcheck:
|
|
67
|
+
test: ["CMD", "wget", "-qO-", "--spider", "http://127.0.0.1/health"]
|
|
68
|
+
interval: 10s
|
|
69
|
+
timeout: 5s
|
|
70
|
+
retries: 3
|
|
71
|
+
start_period: 10s
|
|
72
|
+
container_name: mock-groq
|
|
73
|
+
environment:
|
|
74
|
+
- MOCK_TYPE=groq
|
|
75
|
+
ports:
|
|
76
|
+
- "8084:80"
|
|
77
|
+
volumes:
|
|
78
|
+
- ./tests/mocks/start_nginx.sh:/start_nginx.sh:ro
|
|
79
|
+
networks:
|
|
80
|
+
- e2e-network
|
|
81
|
+
|
|
82
|
+
mock-deepseek:
|
|
83
|
+
image: nginx:alpine
|
|
84
|
+
entrypoint: ["/bin/sh", "/start_nginx.sh"]
|
|
85
|
+
healthcheck:
|
|
86
|
+
test: ["CMD", "wget", "-qO-", "--spider", "http://127.0.0.1/health"]
|
|
87
|
+
interval: 10s
|
|
88
|
+
timeout: 5s
|
|
89
|
+
retries: 3
|
|
90
|
+
start_period: 10s
|
|
91
|
+
container_name: mock-deepseek
|
|
92
|
+
environment:
|
|
93
|
+
- MOCK_TYPE=deepseek
|
|
94
|
+
ports:
|
|
95
|
+
- "8085:80"
|
|
96
|
+
volumes:
|
|
97
|
+
- ./tests/mocks/start_nginx.sh:/start_nginx.sh:ro
|
|
98
|
+
networks:
|
|
99
|
+
- e2e-network
|
|
100
|
+
|
|
101
|
+
mock-openai:
|
|
102
|
+
image: nginx:alpine
|
|
103
|
+
entrypoint: ["/bin/sh", "/start_nginx.sh"]
|
|
104
|
+
healthcheck:
|
|
105
|
+
test: ["CMD", "wget", "-qO-", "--spider", "http://127.0.0.1/health"]
|
|
106
|
+
interval: 10s
|
|
107
|
+
timeout: 5s
|
|
108
|
+
retries: 3
|
|
109
|
+
start_period: 10s
|
|
110
|
+
container_name: mock-openai
|
|
111
|
+
environment:
|
|
112
|
+
- MOCK_TYPE=openai
|
|
113
|
+
ports:
|
|
114
|
+
- "8086:80"
|
|
115
|
+
volumes:
|
|
116
|
+
- ./tests/mocks/start_nginx.sh:/start_nginx.sh:ro
|
|
117
|
+
networks:
|
|
118
|
+
- e2e-network
|
|
119
|
+
|
|
120
|
+
mock-ollama:
|
|
121
|
+
image: nginx:alpine
|
|
122
|
+
entrypoint: ["/bin/sh", "/start_nginx.sh"]
|
|
123
|
+
healthcheck:
|
|
124
|
+
test: ["CMD", "wget", "-qO-", "--spider", "http://127.0.0.1/health"]
|
|
125
|
+
interval: 10s
|
|
126
|
+
timeout: 5s
|
|
127
|
+
retries: 3
|
|
128
|
+
start_period: 10s
|
|
129
|
+
container_name: mock-ollama
|
|
130
|
+
environment:
|
|
131
|
+
- MOCK_TYPE=ollama
|
|
132
|
+
ports:
|
|
133
|
+
- "11434:80"
|
|
134
|
+
volumes:
|
|
135
|
+
- ./tests/mocks/start_nginx.sh:/start_nginx.sh:ro
|
|
136
|
+
networks:
|
|
137
|
+
- e2e-network
|
|
138
|
+
|
|
139
|
+
e2e-tests:
|
|
140
|
+
build:
|
|
141
|
+
context: .
|
|
142
|
+
dockerfile: .github/packaging/docker/Dockerfile
|
|
143
|
+
target: builder
|
|
144
|
+
container_name: cursor-e2e-tests
|
|
145
|
+
depends_on:
|
|
146
|
+
- mock-together
|
|
147
|
+
- mock-cerebras
|
|
148
|
+
- mock-fireworks
|
|
149
|
+
- mock-groq
|
|
150
|
+
- mock-deepseek
|
|
151
|
+
- mock-openai
|
|
152
|
+
- mock-ollama
|
|
153
|
+
environment:
|
|
154
|
+
- TOGETHER_API_KEY=test-key
|
|
155
|
+
- CEREBRAS_API_KEY=test-key
|
|
156
|
+
- FIREWORKS_API_KEY=test-key
|
|
157
|
+
- GROQ_API_KEY=test-key
|
|
158
|
+
- DEEPSEEK_API_KEY=test-key
|
|
159
|
+
- OPENAI_API_KEY=test-key
|
|
160
|
+
- SERPAPI_KEY=test-key
|
|
161
|
+
- OLLAMA_HOST=http://mock-ollama:11434
|
|
162
|
+
- TEST_MODE=1
|
|
163
|
+
- BUILDKIT_INLINE_CACHE=1
|
|
164
|
+
volumes:
|
|
165
|
+
- .:/app
|
|
166
|
+
- /app/build
|
|
167
|
+
- /root/.cache
|
|
168
|
+
working_dir: /app
|
|
169
|
+
user: root
|
|
170
|
+
command:
|
|
171
|
+
- "bash"
|
|
172
|
+
- "-c"
|
|
173
|
+
- "ulimit -c unlimited && /app/tests/e2e/run_tests_in_docker.sh || touch /app/tests/e2e/.e2e_failed"
|
|
174
|
+
security_opt:
|
|
175
|
+
- seccomp:unconfined
|
|
176
|
+
cap_add:
|
|
177
|
+
- SYS_PTRACE
|
|
178
|
+
stop_grace_period: 10s
|
|
179
|
+
stop_signal: SIGTERM
|
|
180
|
+
healthcheck:
|
|
181
|
+
test: ["CMD", "true"]
|
|
182
|
+
interval: 60s
|
|
183
|
+
timeout: 5s
|
|
184
|
+
retries: 1
|
|
185
|
+
restart: "no"
|
|
186
|
+
networks:
|
|
187
|
+
- e2e-network
|
|
188
|
+
deploy:
|
|
189
|
+
resources:
|
|
190
|
+
limits:
|
|
191
|
+
cpus: '2'
|
|
192
|
+
memory: 4G
|
|
193
|
+
pids: 1024
|
|
194
|
+
tmpfs:
|
|
195
|
+
- /tmp:exec,mode=1777
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/expect -f
|
|
2
|
+
|
|
3
|
+
set timeout 30
|
|
4
|
+
set test_passed 1
|
|
5
|
+
set BINARY_PATH $env(BINARY_PATH)
|
|
6
|
+
|
|
7
|
+
# Function to run a test
|
|
8
|
+
proc run_test {test_name mode_selection expected_response} {
|
|
9
|
+
global test_passed BINARY_PATH
|
|
10
|
+
|
|
11
|
+
send_user "Running test: $test_name\n"
|
|
12
|
+
|
|
13
|
+
# Start the agent
|
|
14
|
+
spawn $BINARY_PATH
|
|
15
|
+
expect "Mode"
|
|
16
|
+
|
|
17
|
+
# Select online mode
|
|
18
|
+
send "1\r"
|
|
19
|
+
expect "Provider"
|
|
20
|
+
|
|
21
|
+
# Select the provider
|
|
22
|
+
send "$mode_selection\r"
|
|
23
|
+
expect "Ready"
|
|
24
|
+
|
|
25
|
+
# Send a test message
|
|
26
|
+
send "Hello\r"
|
|
27
|
+
expect {
|
|
28
|
+
"$expected_response" {
|
|
29
|
+
send_user "✓ Test $test_name passed\n"
|
|
30
|
+
}
|
|
31
|
+
timeout {
|
|
32
|
+
send_user "✗ Test $test_name failed: timeout\n"
|
|
33
|
+
set test_passed 0
|
|
34
|
+
}
|
|
35
|
+
eof {
|
|
36
|
+
send_user "✗ Test $test_name failed: unexpected EOF\n"
|
|
37
|
+
set test_passed 0
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
# Exit
|
|
42
|
+
send "exit\r"
|
|
43
|
+
expect eof
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
# Test Fireworks
|
|
47
|
+
run_test "Fireworks" "3" "Mock Fireworks response"
|
|
48
|
+
|
|
49
|
+
# Test Groq
|
|
50
|
+
run_test "Groq" "4" "Mock Groq response"
|
|
51
|
+
|
|
52
|
+
# Test DeepSeek
|
|
53
|
+
run_test "DeepSeek" "5" "Mock DeepSeek response"
|
|
54
|
+
|
|
55
|
+
# Test OpenAI
|
|
56
|
+
run_test "OpenAI" "6" "Mock OpenAI response"
|
|
57
|
+
|
|
58
|
+
# Test Together (existing)
|
|
59
|
+
run_test "Together" "1" "Mock Together AI response"
|
|
60
|
+
|
|
61
|
+
# Test Cerebras (existing)
|
|
62
|
+
run_test "Cerebras" "2" "Mock Cerebras response"
|
|
63
|
+
|
|
64
|
+
if {$test_passed} {
|
|
65
|
+
send_user "All E2E tests passed!\n"
|
|
66
|
+
exit 0
|
|
67
|
+
} else {
|
|
68
|
+
send_user "Some E2E tests failed!\n"
|
|
69
|
+
exit 1
|
|
70
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -e
|
|
3
|
+
set -o pipefail
|
|
4
|
+
|
|
5
|
+
# Cleanup function to ensure proper shutdown
|
|
6
|
+
cleanup() {
|
|
7
|
+
local exit_code=$?
|
|
8
|
+
echo "=== Cleaning up test environment ==="
|
|
9
|
+
# Kill any remaining child processes
|
|
10
|
+
pkill -P $$ 2>/dev/null || true
|
|
11
|
+
# Return the exit code without exiting
|
|
12
|
+
return $exit_code
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
# Set up trap to ensure cleanup runs on exit and preserves exit code
|
|
16
|
+
handle_exit() {
|
|
17
|
+
cleanup
|
|
18
|
+
exit $?
|
|
19
|
+
}
|
|
20
|
+
trap handle_exit EXIT INT TERM
|
|
21
|
+
|
|
22
|
+
# Print environment for debugging
|
|
23
|
+
echo "=== Environment ==="
|
|
24
|
+
printenv | sort
|
|
25
|
+
echo "=================="
|
|
26
|
+
|
|
27
|
+
# Enable core dumps for debugging segfaults
|
|
28
|
+
ulimit -c unlimited
|
|
29
|
+
|
|
30
|
+
# Set test data directory
|
|
31
|
+
export TEST_DATA_DIR="/tmp/cursor_test_data"
|
|
32
|
+
|
|
33
|
+
# Clean up any previous builds
|
|
34
|
+
echo "=== Cleaning previous builds ==="
|
|
35
|
+
rm -rf /tmp/app
|
|
36
|
+
mkdir -p /tmp/app
|
|
37
|
+
|
|
38
|
+
# Copy only the necessary files using cp
|
|
39
|
+
echo "=== Copying application files ==="
|
|
40
|
+
cp -r /app/. /tmp/app/
|
|
41
|
+
rm -rf /tmp/app/build
|
|
42
|
+
rm -rf /tmp/app/.git
|
|
43
|
+
|
|
44
|
+
# Verify expect is installed
|
|
45
|
+
echo "=== Verifying test dependencies ==="
|
|
46
|
+
command -v expect >/dev/null 2>&1 || { echo >&2 "expect is required but not installed. Aborting."; exit 1; }
|
|
47
|
+
command -v python3 >/dev/null 2>&1 || { echo >&2 "python3 is required but not installed. Aborting."; exit 1; }
|
|
48
|
+
|
|
49
|
+
# Wait for mock services to be ready
|
|
50
|
+
echo "=== Waiting for mock services to be ready ==="
|
|
51
|
+
for service in mock-together mock-cerebras mock-fireworks mock-groq mock-deepseek mock-openai mock-ollama; do
|
|
52
|
+
echo "Waiting for $service..."
|
|
53
|
+
timeout 60 bash -c "until wget -qO- http://$service/health 2>/dev/null | grep -q 'healthy'; do sleep 2; done" || {
|
|
54
|
+
echo "Service $service failed to become ready"
|
|
55
|
+
exit 1
|
|
56
|
+
}
|
|
57
|
+
echo "$service is ready"
|
|
58
|
+
done
|
|
59
|
+
|
|
60
|
+
# Build the project
|
|
61
|
+
echo "=== Building the project ==="
|
|
62
|
+
cd /tmp/app
|
|
63
|
+
mkdir -p build
|
|
64
|
+
cd build
|
|
65
|
+
cmake ..
|
|
66
|
+
make -j1
|
|
67
|
+
|
|
68
|
+
# Set binary path for E2E tests
|
|
69
|
+
export BINARY_PATH="/tmp/app/build/bin/cursor-agent"
|
|
70
|
+
|
|
71
|
+
# Run unit tests
|
|
72
|
+
echo "=== Running unit tests ==="
|
|
73
|
+
ctest --output-on-failure
|
|
74
|
+
|
|
75
|
+
# Run E2E tests
|
|
76
|
+
echo "=== Running E2E tests ==="
|
|
77
|
+
cd ../tests/e2e
|
|
78
|
+
chmod +x ./run_e2e_tests.sh
|
|
79
|
+
|
|
80
|
+
# Create test data directory
|
|
81
|
+
mkdir -p "$TEST_DATA_DIR"
|
|
82
|
+
|
|
83
|
+
# Set environment variables for debugging
|
|
84
|
+
export EXPECT_DEBUG=1
|
|
85
|
+
|
|
86
|
+
# Run tests with a longer timeout and better error handling
|
|
87
|
+
set +e
|
|
88
|
+
start_time=$(date +%s)
|
|
89
|
+
timeout 600s ./run_e2e_tests.sh > >(tee -a e2e_test.log) 2>&1
|
|
90
|
+
result=$?
|
|
91
|
+
end_time=$(date +%s)
|
|
92
|
+
duration=$((end_time - start_time))
|
|
93
|
+
set -e
|
|
94
|
+
|
|
95
|
+
# Copy all test artifacts to the host
|
|
96
|
+
cp -r e2e_test.log "$TEST_DATA_DIR/"* /app/tests/e2e/ 2>/dev/null || true
|
|
97
|
+
|
|
98
|
+
# Check test results
|
|
99
|
+
if [ $result -eq 0 ]; then
|
|
100
|
+
echo "=== E2E tests completed successfully in ${duration}s ==="
|
|
101
|
+
echo "=== Test logs saved to /app/tests/e2e/e2e_test.log ==="
|
|
102
|
+
touch /app/tests/e2e/.e2e_success
|
|
103
|
+
# Exit with success code
|
|
104
|
+
exit 0
|
|
105
|
+
else
|
|
106
|
+
echo "=== E2E tests failed after ${duration}s (exit code: $result) ==="
|
|
107
|
+
echo "=== Last 50 lines of test output ==="
|
|
108
|
+
tail -n 50 e2e_test.log || true
|
|
109
|
+
echo "=== Expect debug logs ==="
|
|
110
|
+
cat "$TEST_DATA_DIR/"expect_*.log 2>/dev/null || echo "No debug logs available"
|
|
111
|
+
echo "========================="
|
|
112
|
+
echo "=== Full test log available in /app/tests/e2e/e2e_test.log ==="
|
|
113
|
+
# Exit with failure code
|
|
114
|
+
exit $result
|
|
115
|
+
fi
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#include "version.h"
|
|
2
|
+
#include <gtest/gtest.h>
|
|
3
|
+
|
|
4
|
+
// Basic test to verify the testing framework works
|
|
5
|
+
TEST(BasicTest, SanityCheck) { EXPECT_EQ(1 + 1, 2); }
|
|
6
|
+
|
|
7
|
+
// Test for version functionality
|
|
8
|
+
TEST(VersionTest, VersionCommand) {
|
|
9
|
+
const char *version = Version::get_version();
|
|
10
|
+
EXPECT_STREQ(version, cursor_version_string);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
int main(int argc, char **argv) {
|
|
14
|
+
::testing::InitGoogleTest(&argc, argv);
|
|
15
|
+
return RUN_ALL_TESTS();
|
|
16
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import http.server
|
|
2
|
+
import socketserver
|
|
3
|
+
import json
|
|
4
|
+
import time
|
|
5
|
+
|
|
6
|
+
class Handler(http.server.BaseHTTPRequestHandler):
|
|
7
|
+
def _set_headers(self, content_type='application/json'):
|
|
8
|
+
self.send_response(200)
|
|
9
|
+
self.send_header('Content-type', content_type)
|
|
10
|
+
self.end_headers()
|
|
11
|
+
|
|
12
|
+
def do_HEAD(self):
|
|
13
|
+
"""Handle HEAD requests (used for health checks)"""
|
|
14
|
+
self._set_headers('text/plain')
|
|
15
|
+
|
|
16
|
+
def do_GET(self):
|
|
17
|
+
if self.path == '/' or self.path == '/health':
|
|
18
|
+
self._set_headers('text/plain')
|
|
19
|
+
self.wfile.write(b'healthy\n')
|
|
20
|
+
elif self.path == '/api/tags':
|
|
21
|
+
self._set_headers()
|
|
22
|
+
response = {
|
|
23
|
+
'models': [
|
|
24
|
+
{
|
|
25
|
+
'name': 'llama3',
|
|
26
|
+
'modified_at': '2023-09-23T00:00:00Z',
|
|
27
|
+
'size': 4096,
|
|
28
|
+
'digest': 'mock-digest-123',
|
|
29
|
+
'details': {
|
|
30
|
+
'format': 'gguf',
|
|
31
|
+
'family': 'llama',
|
|
32
|
+
'parameter_size': '8B',
|
|
33
|
+
'quantization_level': 'Q4_0'
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
self.wfile.write(json.dumps(response).encode('utf-8'))
|
|
39
|
+
else:
|
|
40
|
+
self.send_response(404)
|
|
41
|
+
self.end_headers()
|
|
42
|
+
|
|
43
|
+
def do_POST(self):
|
|
44
|
+
content_length = int(self.headers['Content-Length'])
|
|
45
|
+
post_data = self.rfile.read(content_length)
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
data = json.loads(post_data.decode('utf-8'))
|
|
49
|
+
|
|
50
|
+
if self.path == '/api/generate':
|
|
51
|
+
self._set_headers()
|
|
52
|
+
response = {
|
|
53
|
+
'model': data.get('model', 'llama3'),
|
|
54
|
+
'created_at': '2023-09-23T00:00:00Z',
|
|
55
|
+
'response': 'This is a mock response from Ollama.',
|
|
56
|
+
'done': True,
|
|
57
|
+
'context': [0] * 64, # Mock context
|
|
58
|
+
'total_duration': 1000000000, # 1 second in nanoseconds
|
|
59
|
+
'load_duration': 100000000,
|
|
60
|
+
'prompt_eval_count': len(data.get('prompt', '').split()),
|
|
61
|
+
'eval_count': 10,
|
|
62
|
+
'eval_duration': 900000000
|
|
63
|
+
}
|
|
64
|
+
self.wfile.write(json.dumps(response).encode('utf-8'))
|
|
65
|
+
elif self.path == '/api/chat':
|
|
66
|
+
self._set_headers()
|
|
67
|
+
response = {
|
|
68
|
+
'model': data.get('model', 'llama3.2:3b'),
|
|
69
|
+
'created_at': '2023-09-23T00:00:00Z',
|
|
70
|
+
'message': {
|
|
71
|
+
'role': 'assistant',
|
|
72
|
+
'content': 'This is a mock response from Ollama.'
|
|
73
|
+
},
|
|
74
|
+
'done': True,
|
|
75
|
+
'total_duration': 1000000000,
|
|
76
|
+
'load_duration': 100000000,
|
|
77
|
+
'prompt_eval_count': 10,
|
|
78
|
+
'eval_count': 10,
|
|
79
|
+
'eval_duration': 900000000
|
|
80
|
+
}
|
|
81
|
+
self.wfile.write(json.dumps(response).encode('utf-8'))
|
|
82
|
+
else:
|
|
83
|
+
self.send_response(404)
|
|
84
|
+
self.end_headers()
|
|
85
|
+
|
|
86
|
+
except json.JSONDecodeError:
|
|
87
|
+
self.send_response(400)
|
|
88
|
+
self.end_headers()
|
|
89
|
+
self.wfile.write(b'Invalid JSON')
|
|
90
|
+
|
|
91
|
+
def run(server_class=http.server.HTTPServer, handler_class=Handler, port=11434):
|
|
92
|
+
server_address = ('0.0.0.0', port)
|
|
93
|
+
httpd = server_class(server_address, handler_class)
|
|
94
|
+
print(f'Starting mock Ollama server on port {port}...')
|
|
95
|
+
httpd.serve_forever()
|
|
96
|
+
|
|
97
|
+
if __name__ == '__main__':
|
|
98
|
+
run()
|