Skip to content

Commit

Permalink
Handle the unknown-op status from test commands.
Browse files Browse the repository at this point in the history
Calva currently fails silently when the test commands fail due to the
unknown-op status. Here we handle that error and show a warning message
when it occurs. This doesn't address the root cause of the issue, but it
will help users to understand the problem.

This will help to debug #1269
  • Loading branch information
marcomorain committed Oct 28, 2021
1 parent 3377305 commit 5c844d2
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 127 deletions.
39 changes: 39 additions & 0 deletions src/nrepl/cider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@

// https://github.com/clojure-emacs/cider-nrepl/blob/a740583c3aa8b582f3097611787a276775131d32/src/cider/nrepl/middleware/test.clj#L45
export interface TestSummary {
ns: number;
var: number;
test: number;
pass: number;
fail: number;
error: number;
};

// https://github.com/clojure-emacs/cider-nrepl/blob/a740583c3aa8b582f3097611787a276775131d32/src/cider/nrepl/middleware/test.clj#L97-L112
export interface TestResult {
context: string;
index: number;
message: string;
ns: string;
type: string;
var: string;
expected?: string;
'gen-input'?: string;
actual?: string;
diffs?: unknown;
error?: unknown;
line?: number
file?: string;
}

// https://github.com/clojure-emacs/cider-nrepl/blob/a740583c3aa8b582f3097611787a276775131d32/src/cider/nrepl/middleware/test.clj#L45-L46
export interface TestResults {
summary: TestSummary;
results: {
[key: string]: {
[key: string]: TestResult[]
}
}
'testing-ns'?: string
'gen-input': unknown
}
122 changes: 47 additions & 75 deletions src/nrepl/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as net from "net";
import { BEncoderStream, BDecoderStream } from "./bencode";
import * as cider from './cider'
import * as state from './../state';
import * as util from '../utilities';
import { PrettyPrintingOptions, disabledPrettyPrinter, getServerSidePrinter } from "../printer";
Expand All @@ -13,6 +14,28 @@ import { getStateValue, prettyPrint } from '../../out/cljs-lib/cljs-lib';
import { getConfig } from '../config';
import { log, Direction, loggingEnabled } from './logging';

function hasStatus(res: any, status: string): boolean {
return res.status && res.status.indexOf(status) > -1;
}

// When a command fails becuase of an unknown-op (usually caused by missing
// middleware), we can mark the operation as failed, so that we can show a message
// in the UI to the user.
// https://nrepl.org/nrepl/design/handlers.html
// If the handler being used by an nREPL server does not recognize or cannot
// perform the operation indicated by a request message’s :op, then it should
// respond with a message containing a :status of "unknown-op".
function resultHandler(resolve: any, reject: any) {
return (res: any): boolean => {
if (hasStatus(res, "unknown-op")) {
reject("The server does not recognize or cannot perform the '" + res.op + "' operation");
} else {
resolve(res);
}
return true;
}
}

/** An nREPL client */
export class NReplClient {
private _nextId = 0;
Expand Down Expand Up @@ -241,32 +264,23 @@ export class NReplSession {
describe(verbose?: boolean) {
return new Promise<any>((resolve, reject) => {
let id = this.client.nextId;
this.messageHandlers[id] = (msg) => {
resolve(msg);
return true;
}
this.messageHandlers[id] = resultHandler(resolve, reject);
this.client.write({ op: "describe", id: id, session: this.sessionId, verbose });
})
}

listSessions() {
return new Promise<any>((resolve, reject) => {
let id = this.client.nextId;
this.messageHandlers[id] = (msg) => {
resolve(msg);
return true;
}
this.messageHandlers[id] = resultHandler(resolve, reject);
this.client.write({ op: "ls-sessions", id: id, session: this.sessionId });
})
}

stacktrace() {
return new Promise<any>((resolve, reject) => {
let id = this.client.nextId;
this.messageHandlers[id] = (msg) => {
resolve(msg);
return true;
}
this.messageHandlers[id] = resultHandler(resolve, reject);
this.client.write({ op: "stacktrace", id, session: this.sessionId });
})
}
Expand Down Expand Up @@ -326,10 +340,7 @@ export class NReplSession {
}
let id = this.client.nextId;
return new Promise<void>((resolve, reject) => {
this.messageHandlers[id] = (msg) => {
resolve();
return true;
}
this.messageHandlers[id] = resultHandler(resolve, reject);
this.client.write({ op: "interrupt", session: this.sessionId, "interrupt-id": interruptId, id })
});
}
Expand Down Expand Up @@ -369,10 +380,7 @@ export class NReplSession {
return new Promise<any>((resolve, reject) => {
const id = this.client.nextId,
extraOpts = getConfig().enableJSCompletions ? { "enhanced-cljs-completion?": "t" } : {};
this.messageHandlers[id] = (msg) => {
resolve(msg);
return true;
}
this.messageHandlers[id] = resultHandler(resolve, reject);
this.client.write({
op: "complete",
ns,
Expand All @@ -388,32 +396,23 @@ export class NReplSession {
info(ns: string, symbol: string) {
return new Promise<any>((resolve, reject) => {
let id = this.client.nextId;
this.messageHandlers[id] = (msg) => {
resolve(msg);
return true;
}
this.messageHandlers[id] = resultHandler(resolve, reject);
this.client.write({ op: "info", ns, symbol, id, session: this.sessionId })
})
}

classpath() {
return new Promise<any>((resolve, reject) => {
let id = this.client.nextId;
this.messageHandlers[id] = (msg) => {
resolve(msg);
return true;
}
this.messageHandlers[id] = resultHandler(resolve, reject);
this.client.write({ op: "classpath", id, session: this.sessionId })
})
}

test(ns: string, test: string) {
test(ns: string, test: string): Promise<cider.TestResults> {
return new Promise<any>((resolve, reject) => {
const id = this.client.nextId;
this.messageHandlers[id] = (msg) => {
resolve(msg);
return true;
};
this.messageHandlers[id] = resultHandler(resolve, reject);
this.client.write({
op: "test-var-query",
ns,
Expand All @@ -431,12 +430,9 @@ export class NReplSession {
}

testNs(ns: string) {
return new Promise<any>((resolve, reject) => {
return new Promise<cider.TestResults>((resolve, reject) => {
let id = this.client.nextId;
this.messageHandlers[id] = (msg) => {
resolve(msg);
return true;
}
this.messageHandlers[id] = resultHandler(resolve, reject);
this.client.write({
op: "test-var-query", ns, id, session: this.sessionId, "var-query": {
"ns-query": {
Expand All @@ -448,56 +444,41 @@ export class NReplSession {
}

testAll() {
return new Promise<any>((resolve, reject) => {
return new Promise<cider.TestResults>((resolve, reject) => {
let id = this.client.nextId;
this.messageHandlers[id] = (msg) => {
resolve(msg);
return true;
}
this.messageHandlers[id] = resultHandler(resolve, reject);
this.client.write({ op: "test-all", id, session: this.sessionId, "load?": true });
})
}

retest() {
return new Promise<any>((resolve, reject) => {
return new Promise<cider.TestResults>((resolve, reject) => {
let id = this.client.nextId;
this.messageHandlers[id] = (msg) => {
resolve(msg);
return true;
}
this.messageHandlers[id] = resultHandler(resolve, reject);
this.client.write({ op: "retest", id, session: this.sessionId });
})
}

loadAll() {
return new Promise<any>((resolve, reject) => {
let id = this.client.nextId;
this.messageHandlers[id] = (msg) => {
resolve(msg);
return true;
}
this.messageHandlers[id] = resultHandler(resolve, reject);
this.client.write({ op: "ns-load-all", id, session: this.sessionId });
})
}

listNamespaces(regexps: string[]) {
return new Promise<any>((resolve, reject) => {
let id = this.client.nextId;
this.messageHandlers[id] = (msg) => {
resolve(msg);
return true;
}
this.messageHandlers[id] = resultHandler(resolve, reject);
this.client.write({ op: "ns-list", id, session: this.sessionId, "filter-regexps": regexps });
})
}

nsPath(ns: string) {
return new Promise<any>((resolve, reject) => {
let id = this.client.nextId;
this.messageHandlers[id] = (msg) => {
resolve(msg);
return true;
}
this.messageHandlers[id] = resultHandler(resolve, reject);
this.client.write({ op: "ns-path", id, ns, session: this.sessionId });
})
}
Expand Down Expand Up @@ -543,10 +524,7 @@ export class NReplSession {
formatCode(code: string, options?: string) {
return new Promise<any>((resolve, reject) => {
let id = this.client.nextId;
this.messageHandlers[id] = (msg) => {
resolve(msg);
return true;
}
this.messageHandlers[id] = resultHandler(resolve, reject);
this.client.write({ op: "format-code", code, options })
});
}
Expand Down Expand Up @@ -576,12 +554,9 @@ export class NReplSession {
}

sendDebugInput(input: any, debugResponseId: string, debugResponseKey: string): Promise<any> {
return new Promise<any>((resolve, _) => {
return new Promise<any>((resolve, reject) => {

this.messageHandlers[debugResponseId] = (response) => {
resolve(response);
return true;
};
this.messageHandlers[debugResponseId] = resultHandler(resolve, reject);

const data: any = {
id: debugResponseId,
Expand All @@ -596,12 +571,9 @@ export class NReplSession {
}

listDebugInstrumentedDefs(): Promise<any> {
return new Promise<any>((resolve, _) => {
return new Promise<any>((resolve, reject) => {
let id = this.client.nextId;
this.messageHandlers[id] = (msg) => {
resolve(msg);
return true;
}
this.messageHandlers[id] = resultHandler(resolve, reject);
this.client.write({ op: 'debug-instrumented-defs', id, session: this.sessionId });
});
}
Expand Down
Loading

0 comments on commit 5c844d2

Please sign in to comment.