Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
205 changes: 190 additions & 15 deletions tests/browser/compiler-explorer.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -395,50 +395,225 @@ describe("toolchain harnesses", { concurrency: true }, () => {
},
);

// A degree-three node distinguishes rotation from simple reversal.
const cloneGraphSpec = {
...judges["clone-graph"],
cases: [
...judges["clone-graph"].cases,
{
label: "three-neighbor star",
input: [[[2, 3, 4], [1], [1], [1]]],
expected: [[2, 3, 4], [1], [1], [1]],
},
],
};

for (const [order, neighborIndex] of [
["original neighbor order", "i"],
["reversed neighbors", "s->numNeighbors - 1 - i"],
[
"reversed neighbors on odd nodes",
"s->val % 2 ? s->numNeighbors - 1 - i : i",
],
["rotated neighbors", "(i + 1) % s->numNeighbors"],
]) {
it(
`C Clone Graph harness accepts numNeighbors with ${order}`,
{ skip: noCc },
async () => {
const dir = mkdtempSync(join(tmpdir(), "codetrial-c-"));
try {
const source = join(dir, "clonegraph.c");
const binary = join(dir, "clonegraph");
writeFileSync(
source,
generateHarness(
"c",
cloneGraphSpec,
`static struct Node* copies[101];
static struct Node* copyNode(struct Node* s) {
if (!s) return NULL;
if (copies[s->val]) return copies[s->val];
struct Node* copy = calloc(1, sizeof(*copy));
copy->val = s->val;
copies[s->val] = copy;
copy->numNeighbors = s->numNeighbors;
copy->neighbors = calloc(s->numNeighbors + 1, sizeof(*copy->neighbors));
for (int i = 0; i < s->numNeighbors; i++)
copy->neighbors[i] = copyNode(s->neighbors[${neighborIndex}]);
return copy;
}
struct Node* replicateTopology(struct Node* s) {
memset(copies, 0, sizeof(copies));
return copyNode(s);
}`,
),
);
await run("cc", ["-std=c17", source, "-o", binary]);
assert.deepEqual(
parseCompilerResults(
(await run(binary, [], { encoding: "utf8" })).stdout,
).results.map((result) => result.actual),
cloneGraphSpec.cases.map((testCase) => testCase.expected),
);
} finally {
rmSync(dir, { recursive: true, force: true });
}
},
);
}

it(
"C Clone Graph harness accepts the numNeighbors field its starter names",
"C Clone Graph harness rejects original nodes",
{ skip: noCc },
async () => {
const dir = mkdtempSync(join(tmpdir(), "codetrial-c-"));
try {
const source = join(dir, "clonegraph.c");
const binary = join(dir, "clonegraph");
const source = join(dir, "clonegraph-original.c");
const binary = join(dir, "clonegraph-original");
writeFileSync(
source,
generateHarness(
"c",
judges["clone-graph"],
`static struct Node* copies[101];
cloneGraphSpec,
"struct Node* replicateTopology(struct Node* s) { return s; }",
),
);
await run("cc", ["-std=c17", source, "-o", binary]);
assert.deepEqual(
parseCompilerResults(
(await run(binary, [], { encoding: "utf8" })).stdout,
).results.map((result) => result.actual),
cloneGraphSpec.cases.map((testCase) =>
testCase.input[0].length
? "Returned graph must be a deep copy."
: [],
),
);
} finally {
rmSync(dir, { recursive: true, force: true });
}
},
);

// A non-root NULL must be rejected before any adjacency rows are emitted.
for (const [position, nodeValue] of [
["the root node", 1],
["a reachable non-root node", 2],
]) {
it(
`C Clone Graph harness reports a NULL neighbor on ${position}`,
{ skip: noCc },
async () => {
const dir = mkdtempSync(join(tmpdir(), "codetrial-c-graph-null-"));
try {
const source = join(dir, "clonegraph-null.c");
const binary = join(dir, "clonegraph-null");
writeFileSync(
source,
generateHarness(
"c",
cloneGraphSpec,
`static struct Node* copies[101];
static struct Node* copyNode(struct Node* s) {
if (!s) return NULL;
if (copies[s->val]) return copies[s->val];
struct Node* copy = calloc(1, sizeof(*copy));
copy->val = s->val;
copies[s->val] = copy;
copy->numNeighbors = s->numNeighbors;
copy->neighbors = calloc(s->numNeighbors + 1, sizeof(*copy->neighbors));
for (int i = 0; i < s->numNeighbors; i++)
copy->neighbors = calloc(s->numNeighbors, sizeof(*copy->neighbors));
for (int i = 0; i < s->numNeighbors; i++) {
if (s->val == ${nodeValue} && i == s->numNeighbors - 1) continue;
copy->neighbors[i] = copyNode(s->neighbors[i]);
}
return copy;
}
struct Node* replicateTopology(struct Node* s) {
memset(copies, 0, sizeof(copies));
return copyNode(s);
}`,
),
);
await run("cc", ["-std=c17", source, "-o", binary]);
assert.deepEqual(
parseCompilerResults(
(await run(binary, [], { encoding: "utf8" })).stdout,
).results.map((result) => result.actual),
cloneGraphSpec.cases.map((testCase) =>
testCase.input[0][nodeValue - 1]?.length
? "Returned graph has a NULL neighbor."
: testCase.expected,
),
);
} finally {
rmSync(dir, { recursive: true, force: true });
}
},
);
}

for (const [behavior, nullNeighborValue] of [
["accepts reordered neighbors", null],
["reports a NULL neighbor on the root node", 1],
["reports a NULL neighbor on a reachable non-root node", 2],
]) {
it(`C++ Clone Graph harness ${behavior}`, { skip: noGpp }, async () => {
const dir = mkdtempSync(join(tmpdir(), "codetrial-cpp-graph-"));
try {
const source = join(dir, "clonegraph.cpp");
const binary = join(dir, "clonegraph");
writeFileSync(
source,
generateHarness(
"cpp",
cloneGraphSpec,
`class Solution {
map<Node*, Node*> copies;
Node* copyNode(Node* node) {
if (!node) return nullptr;
if (copies.count(node)) return copies[node];
auto* copy = new Node(node->val);
copies[node] = copy;
copy->neighbors.resize(node->neighbors.size(), nullptr);
for (size_t i = 0; i < node->neighbors.size(); i++) {
if (node->val == ${nullNeighborValue ?? 0} && i + 1 == node->neighbors.size()) continue;
copy->neighbors[i] = copyNode(node->neighbors[node->neighbors.size() - 1 - i]);
}
return copy;
}
public:
Node* replicateTopology(Node* node) { return copyNode(node); }
};`,
),
);
await run("cc", ["-std=c17", source, "-o", binary]);
assert.deepEqual(
parseCompilerResults(
(await run(binary, [], { encoding: "utf8" })).stdout,
).results.map((result) => result.actual),
judges["clone-graph"].cases.map((testCase) => testCase.expected),
await run("g++", ["-std=c++20", source, "-o", binary]);
const { results } = parseCompilerResults(
(await run(binary, [], { encoding: "utf8" })).stdout,
);
assert.equal(results.length, cloneGraphSpec.cases.length);
cloneGraphSpec.cases.forEach((testCase, index) => {
const result = results[index];
if (
nullNeighborValue !== null &&
testCase.input[0][nullNeighborValue - 1]?.length
) {
assert.equal(
result.error,
"Returned graph has a NULL neighbor.",
testCase.label,
);
assert.equal(result.actual, undefined, testCase.label);
} else {
assert.equal(result.error, undefined, testCase.label);
assert.deepEqual(result.actual, testCase.expected, testCase.label);
}
});
} finally {
rmSync(dir, { recursive: true, force: true });
}
},
);
});
}

it(
"C Merge k Sorted Lists harness can produce passing Compiler Explorer JSON",
Expand Down
5 changes: 3 additions & 2 deletions web/compiler-explorer.js
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,8 @@ static void json_string_matrix(FILE* out, char*** values, int size, int* colSize
static void json_list(FILE* out, struct ListNode* node) { for (struct ListNode *slow = node, *fast = node; fast && fast->next; ) { slow = slow->next; fast = fast->next->next; if (slow == fast) { fputs("null,\\"error\\":", out); json_string(out, "Returned list must not reach a node twice."); return; } } fputc('[', out); for (int i = 0; node; node = node->next, i++) { if (i) fputc(',', out); fprintf(out, "%d", node->val); } fputc(']', out); }
static void json_tree(FILE* out, struct TreeNode* root) { if (!root) { fputs("[]", out); return; } struct TreeNode* queue[4096]; const char* values[4096]; char numbers[4096][32]; int head = 0, tail = 0, count = 0; queue[tail++] = root; while (head < tail && tail < 4096) { struct TreeNode* node = queue[head++]; if (!node) { values[count++] = "null"; continue; } snprintf(numbers[count], sizeof(numbers[count]), "%d", node->val); values[count] = numbers[count]; count++; queue[tail++] = node->left; queue[tail++] = node->right; } while (count > 0 && strcmp(values[count - 1], "null") == 0) count--; fputc('[', out); for (int i = 0; i < count; i++) { if (i) fputc(',', out); fputs(values[i], out); } fputc(']', out); }
static void json_random_list(FILE* out, struct Node* node) { struct Node* nodes[10000]; int count = 0; for (struct Node* cur = node; cur && count < 10000; cur = cur->next) { if (isOriginalRandomNode(cur)) { json_string(out, "Returned list must be a deep copy."); return; } nodes[count++] = cur; } fputc('[', out); for (int i = 0; i < count; i++) { if (i) fputc(',', out); int random = -1; for (int j = 0; j < count; j++) if (nodes[i]->random == nodes[j]) random = j; fprintf(out, "[%d,", nodes[i]->val); if (nodes[i]->random && random < 0) { json_string(out, "Returned random pointer must stay inside the copied list."); fputc(']', out); continue; } if (random < 0) fputs("null", out); else fprintf(out, "%d", random); fputc(']', out); } fputc(']', out); }
static void json_graph(FILE* out, struct Node* node) { if (!node) { fputs("[]", out); return; } struct Node* nodes[1024]; bool seen[1024] = {0}; int count = 0; nodes[count++] = node; for (int i = 0; i < count && count < 1024; i++) { struct Node* cur = nodes[i]; if (isOriginalGraphNode(cur)) { json_string(out, "Returned graph must be a deep copy."); return; } if (cur->val > 0 && cur->val < 1024) seen[cur->val] = true; for (int j = 0; j < cur->numNeighbors; j++) { struct Node* next = cur->neighbors[j]; if (next && next->val > 0 && next->val < 1024 && !seen[next->val]) { seen[next->val] = true; nodes[count++] = next; } } } fputc('[', out); for (int value = 1, printed = 0; value < 1024; value++) { struct Node* cur = NULL; for (int i = 0; i < count; i++) if (nodes[i]->val == value) cur = nodes[i]; if (!cur) continue; if (printed++) fputc(',', out); fputc('[', out); for (int j = 0; j < cur->numNeighbors; j++) { if (j) fputc(',', out); fprintf(out, "%d", cur->neighbors[j]->val); } fputc(']', out); } fputc(']', out); }
static int compare_graph_neighbor_ids(const void* a, const void* b) { int left = *(const int*)a; int right = *(const int*)b; return (left > right) - (left < right); }
static void json_graph(FILE* out, struct Node* node) { if (!node) { fputs("[]", out); return; } struct Node* nodes[1024]; bool seen[1024] = {0}; int count = 0; nodes[count++] = node; for (int i = 0; i < count && count < 1024; i++) { struct Node* cur = nodes[i]; if (isOriginalGraphNode(cur)) { json_string(out, "Returned graph must be a deep copy."); return; } if (cur->val > 0 && cur->val < 1024) seen[cur->val] = true; for (int j = 0; j < cur->numNeighbors; j++) { struct Node* next = cur->neighbors[j]; if (!next) { json_string(out, "Returned graph has a NULL neighbor."); return; } if (next->val > 0 && next->val < 1024 && !seen[next->val]) { seen[next->val] = true; nodes[count++] = next; } } } fputc('[', out); for (int value = 1, printed = 0; value < 1024; value++) { struct Node* cur = NULL; for (int i = 0; i < count; i++) if (nodes[i]->val == value) cur = nodes[i]; if (!cur) continue; if (printed++) fputc(',', out); int degree = cur->numNeighbors; int* neighborIds = NULL; if (degree > 0) { neighborIds = malloc((size_t)degree * sizeof(*neighborIds)); if (!neighborIds) exit(EXIT_FAILURE); for (int j = 0; j < degree; j++) neighborIds[j] = cur->neighbors[j]->val; qsort(neighborIds, (size_t)degree, sizeof(*neighborIds), compare_graph_neighbor_ids); } json_int_array(out, neighborIds, degree); free(neighborIds); } fputc(']', out); }
static void json_next_tree(FILE* out, struct Node* root) { if (!root) { fputs("[]", out); return; } struct Node* queue[4096]; int head = 0, tail = 0, firstLevel = 1; queue[tail++] = root; fputc('[', out); while (head < tail) { int end = tail; if (!firstLevel) fputc(',', out); firstLevel = 0; fputc('[', out); for (int i = head; i < end; i++) { if (i > head) fputc(',', out); fprintf(out, "%d", queue[i]->val); if (queue[i]->next != (i + 1 < end ? queue[i + 1] : NULL)) { fputs("]", out); fputs("]", out); return; } if (queue[i]->left) queue[tail++] = queue[i]->left; if (queue[i]->right) queue[tail++] = queue[i]->right; } fputc(']', out); head = end; } fputc(']', out); }
static void json_quad_tree_visit(FILE* out, struct Node* node, int* printed) { if (!node) return; if ((*printed)++) fputc(',', out); fprintf(out, "[%d,%d]", node->isLeaf ? 1 : 0, node->isLeaf ? (node->val ? 1 : 0) : 1); if (!node->isLeaf) { json_quad_tree_visit(out, node->topLeft, printed); json_quad_tree_visit(out, node->topRight, printed); json_quad_tree_visit(out, node->bottomLeft, printed); json_quad_tree_visit(out, node->bottomRight, printed); } }
static void json_quad_tree(FILE* out, struct Node* root) { int printed = 0; fputc('[', out); json_quad_tree_visit(out, root, &printed); fputc(']', out); }
Expand Down Expand Up @@ -540,7 +541,7 @@ TreeNode* findTreeNode(TreeNode* root, int value) { queue<TreeNode*> q; if (root
Node* randomList(vector<pair<int, optional<int>>> items) { vector<Node*> nodes; for (auto item : items) nodes.push_back(new Node(item.first)); originalRandomNodes = set<Node*>(nodes.begin(), nodes.end()); for (size_t i = 0; i < nodes.size(); ++i) { nodes[i]->next = i + 1 < nodes.size() ? nodes[i + 1] : nullptr; if (items[i].second) nodes[i]->random = nodes[*items[i].second]; } return nodes.empty() ? nullptr : nodes[0]; }
string randomListToJson(Node* node) { vector<Node*> nodes; map<Node*, int> index; set<Node*> seen; for (auto* cur = node; cur && !seen.count(cur); cur = cur->next) { if (originalRandomNodes.count(cur)) throw runtime_error("Returned list must be a deep copy."); seen.insert(cur); index[cur] = nodes.size(); nodes.push_back(cur); } string out = "["; for (size_t i = 0; i < nodes.size(); ++i) { if (i) out += ","; if (nodes[i]->random && !index.count(nodes[i]->random)) throw runtime_error("Returned random pointer must stay inside the copied list."); out += "[" + to_string(nodes[i]->val) + "," + (nodes[i]->random ? to_string(index[nodes[i]->random]) : string("null")) + "]"; } return out + "]"; }
Node* graphNode(vector<vector<int>> adjacency) { if (adjacency.empty()) { originalGraphNodes.clear(); return nullptr; } vector<Node*> nodes; for (size_t i = 0; i < adjacency.size(); ++i) nodes.push_back(new Node(i + 1)); originalGraphNodes = set<Node*>(nodes.begin(), nodes.end()); for (size_t i = 0; i < adjacency.size(); ++i) for (int value : adjacency[i]) nodes[i]->neighbors.push_back(nodes[value - 1]); return nodes[0]; }
string graphToJson(Node* node) { if (!node) return "[]"; map<int, Node*> byValue; queue<Node*> q; q.push(node); while (!q.empty()) { auto* cur = q.front(); q.pop(); if (!cur) continue; if (originalGraphNodes.count(cur)) throw runtime_error("Returned graph must be a deep copy."); if (byValue.count(cur->val)) continue; byValue[cur->val] = cur; for (auto* next : cur->neighbors) q.push(next); } string out = "["; bool first = true; for (auto& [value, cur] : byValue) { if (!first) out += ","; first = false; vector<int> neighbors; for (auto* next : cur->neighbors) neighbors.push_back(next->val); sort(neighbors.begin(), neighbors.end()); out += toJson(neighbors); } return out + "]"; }
string graphToJson(Node* node) { if (!node) return "[]"; map<int, Node*> byValue; queue<Node*> q; q.push(node); while (!q.empty()) { auto* cur = q.front(); q.pop(); if (!cur) continue; if (originalGraphNodes.count(cur)) throw runtime_error("Returned graph must be a deep copy."); if (byValue.count(cur->val)) continue; byValue[cur->val] = cur; for (auto* next : cur->neighbors) { if (!next) throw runtime_error("Returned graph has a NULL neighbor."); q.push(next); } } string out = "["; bool first = true; for (auto& [value, cur] : byValue) { if (!first) out += ","; first = false; vector<int> neighbors; for (auto* next : cur->neighbors) neighbors.push_back(next->val); sort(neighbors.begin(), neighbors.end()); out += toJson(neighbors); } return out + "]"; }
Node* nextTree(vector<optional<int>> values) { if (values.empty() || !values[0]) return nullptr; auto* root = new Node(*values[0]); queue<Node*> q; q.push(root); size_t i = 1; while (!q.empty() && i < values.size()) { auto* node = q.front(); q.pop(); if (i < values.size() && values[i]) { node->left = new Node(*values[i]); q.push(node->left); } ++i; if (i < values.size() && values[i]) { node->right = new Node(*values[i]); q.push(node->right); } ++i; } return root; }
string nextTreeToJson(Node* root) { vector<string> levels; queue<Node*> q; if (root) q.push(root); while (!q.empty()) { size_t count = q.size(); vector<Node*> level; for (size_t i = 0; i < count; ++i) { auto* node = q.front(); q.pop(); level.push_back(node); if (node->left) q.push(node->left); if (node->right) q.push(node->right); } vector<int> row; for (size_t i = 0; i < level.size(); ++i) { if (level[i]->next != (i + 1 < level.size() ? level[i + 1] : nullptr)) throw runtime_error("next pointers do not match the tree level order."); row.push_back(level[i]->val); } levels.push_back(toJson(row)); } string out = "["; for (size_t i = 0; i < levels.size(); ++i) { if (i) out += ","; out += levels[i]; } return out + "]"; }
string quadTreeToJson(Node* root) { vector<string> rows; function<void(Node*)> visit = [&](Node* node) { if (!node) return; rows.push_back("[" + to_string(node->isLeaf ? 1 : 0) + "," + to_string(node->isLeaf ? (node->val ? 1 : 0) : 1) + "]"); if (!node->isLeaf) { visit(node->topLeft); visit(node->topRight); visit(node->bottomLeft); visit(node->bottomRight); } }; visit(root); string out = "["; for (size_t i = 0; i < rows.size(); ++i) { if (i) out += ","; out += rows[i]; } return out + "]"; }`;
Expand Down