From 0d49e81e2964fb2de3ad6d90274312f4f319bf87 Mon Sep 17 00:00:00 2001 From: Peter Thomas Date: Tue, 10 Oct 2023 15:53:48 +0530 Subject: [PATCH] separate out debug server-side into new project --- .../src/main/java/com/intuit/karate/Main.java | 19 +- .../intuit/karate/core/ScenarioEngine.java | 7 +- .../com/intuit/karate/debug/Breakpoint.java | 98 ---- .../com/intuit/karate/debug/DapClient.java | 60 -- .../intuit/karate/debug/DapClientHandler.java | 53 -- .../com/intuit/karate/debug/DapDecoder.java | 94 ---- .../com/intuit/karate/debug/DapEncoder.java | 60 -- .../com/intuit/karate/debug/DapMessage.java | 186 ------- .../com/intuit/karate/debug/DapServer.java | 107 ---- .../intuit/karate/debug/DapServerHandler.java | 526 ------------------ .../com/intuit/karate/debug/DebugThread.java | 279 ---------- .../karate/debug/SourceBreakpoints.java | 98 ---- .../com/intuit/karate/debug/StackFrame.java | 67 --- .../intuit/karate/debug/DapClientRunner.java | 12 - .../intuit/karate/debug/DapServerRunner.java | 17 - 15 files changed, 17 insertions(+), 1666 deletions(-) delete mode 100644 karate-core/src/main/java/com/intuit/karate/debug/Breakpoint.java delete mode 100644 karate-core/src/main/java/com/intuit/karate/debug/DapClient.java delete mode 100644 karate-core/src/main/java/com/intuit/karate/debug/DapClientHandler.java delete mode 100644 karate-core/src/main/java/com/intuit/karate/debug/DapDecoder.java delete mode 100644 karate-core/src/main/java/com/intuit/karate/debug/DapEncoder.java delete mode 100644 karate-core/src/main/java/com/intuit/karate/debug/DapMessage.java delete mode 100644 karate-core/src/main/java/com/intuit/karate/debug/DapServer.java delete mode 100644 karate-core/src/main/java/com/intuit/karate/debug/DapServerHandler.java delete mode 100644 karate-core/src/main/java/com/intuit/karate/debug/DebugThread.java delete mode 100644 karate-core/src/main/java/com/intuit/karate/debug/SourceBreakpoints.java delete mode 100644 karate-core/src/main/java/com/intuit/karate/debug/StackFrame.java delete mode 100644 karate-core/src/test/java/com/intuit/karate/debug/DapClientRunner.java delete mode 100644 karate-core/src/test/java/com/intuit/karate/debug/DapServerRunner.java diff --git a/karate-core/src/main/java/com/intuit/karate/Main.java b/karate-core/src/main/java/com/intuit/karate/Main.java index b30dc7a5a..95fd74129 100644 --- a/karate-core/src/main/java/com/intuit/karate/Main.java +++ b/karate-core/src/main/java/com/intuit/karate/Main.java @@ -25,7 +25,6 @@ import com.intuit.karate.core.MockServer; import com.intuit.karate.core.RuntimeHookFactory; -import com.intuit.karate.debug.DapServer; import com.intuit.karate.http.HttpServer; import com.intuit.karate.http.RequestHandler; import com.intuit.karate.http.ServerConfig; @@ -126,9 +125,6 @@ public class Main implements Callable { description = "debug mode (optional port else dynamically chosen)") int debugPort; - @Option(names = {"--debug-keepalive"}, defaultValue = "false", arity = "0..1", fallbackValue = "true", description = "keep debug server open for connections after disconnect") - boolean keepDebugServerAlive; - @Option(names = {"-D", "--dryrun"}, description = "dry run, generate html reports only") boolean dryRun; @@ -200,10 +196,10 @@ public static Main parseKarateOptions(String line) { String[] args = Command.tokenize(line); return CommandLine.populateCommand(new Main(), args); } - + public static Main parseKarateArgs(List args) { return CommandLine.populateCommand(new Main(), args.toArray(new String[args.size()])); - } + } // matches ( -X XXX )* (XXX) private static final Pattern CLI_ARGS = Pattern.compile("(\\s*-{1,2}\\w\\s\\S*\\s*)*(.*)$"); @@ -322,8 +318,15 @@ public Void call() throws Exception { logger.info("deleted directory: {}", output); } if (debugPort != -1) { - DapServer server = new DapServer(debugPort, !keepDebugServerAlive); - server.waitSync(); + try { + Class clazz = Class.forName("io.karatelabs.debug.Main"); + Method method = clazz.getMethod("main", String[].class); + String[] params = new String[]{debugPort + ""}; + method.invoke(null, (Object) params); + } catch (Exception e) { + String message = "error: debug server failed, is 'karate-debugserver' added as a dependency ?"; + System.out.println(message); + } return null; } if (paths != null) { diff --git a/karate-core/src/main/java/com/intuit/karate/core/ScenarioEngine.java b/karate-core/src/main/java/com/intuit/karate/core/ScenarioEngine.java index da971702f..6fc31b2a1 100644 --- a/karate-core/src/main/java/com/intuit/karate/core/ScenarioEngine.java +++ b/karate-core/src/main/java/com/intuit/karate/core/ScenarioEngine.java @@ -760,7 +760,12 @@ private Channel channel(String type) { } catch (KarateException ke) { throw ke; } catch (Exception e) { - String message = "cannot instantiate " + type + ", is 'karate-" + type + "' included as a maven / gradle dependency ? " + e.getMessage(); + String message; + if (e instanceof ClassNotFoundException) { + message = "cannot instantiate [" + type + "], is 'karate-" + type + "' included as a maven / gradle dependency ?"; + } else { + message = e.getMessage(); + } logger.error(message); throw new RuntimeException(message, e); } diff --git a/karate-core/src/main/java/com/intuit/karate/debug/Breakpoint.java b/karate-core/src/main/java/com/intuit/karate/debug/Breakpoint.java deleted file mode 100644 index bd30aa566..000000000 --- a/karate-core/src/main/java/com/intuit/karate/debug/Breakpoint.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * The MIT License - * - * Copyright 2022 Karate Labs Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.intuit.karate.debug; - -import com.intuit.karate.core.FeatureParser; -import com.intuit.karate.core.Step; - -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * - * @author pthomas3 - */ -public class Breakpoint { - - private static final AtomicInteger NEXT = new AtomicInteger(); - - public final int id; - public final int line; - public final boolean verified; - public final String condition; - - public Breakpoint(Map map) { - id = NEXT.incrementAndGet(); - line = (Integer) map.get("line"); - verified = true; - String breakpointCondition = (String) map.get("condition"); - if (breakpointCondition != null) { - // remove cucumber prefix - String conditionText = breakpointCondition.trim(); - for (String prefix : Step.PREFIXES) { - if (conditionText.startsWith(prefix)) { - conditionText = conditionText.substring(prefix.length()); - break; - } - } - conditionText = conditionText.trim(); - // if docstring to get the syntax highlight, remove the triple quotes - if (conditionText.startsWith(FeatureParser.TRIPLE_QUOTES) && conditionText.endsWith(FeatureParser.TRIPLE_QUOTES)) { - conditionText = conditionText.substring(FeatureParser.TRIPLE_QUOTES.length()); - conditionText = conditionText.substring(0, (conditionText.length() - FeatureParser.TRIPLE_QUOTES.length())); - } - condition = conditionText.trim(); - } else { - condition = null; - } - } - - public int getId() { - return id; - } - - public int getLine() { - return line; - } - - public Map toMap() { - Map map = new HashMap(); - map.put("id", id); - map.put("line", line); - map.put("verified", verified); - return map; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("[id: ").append(id); - sb.append(", line: ").append(line); - sb.append(", verified: ").append(verified); - sb.append("]"); - return sb.toString(); - } - -} diff --git a/karate-core/src/main/java/com/intuit/karate/debug/DapClient.java b/karate-core/src/main/java/com/intuit/karate/debug/DapClient.java deleted file mode 100644 index 13fd371a8..000000000 --- a/karate-core/src/main/java/com/intuit/karate/debug/DapClient.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * The MIT License - * - * Copyright 2022 Karate Labs Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.intuit.karate.debug; - -import io.netty.bootstrap.Bootstrap; -import io.netty.channel.Channel; -import io.netty.channel.ChannelFuture; -import io.netty.channel.ChannelFutureListener; -import io.netty.channel.ChannelInitializer; -import io.netty.channel.ChannelPipeline; -import io.netty.channel.nio.NioEventLoopGroup; -import io.netty.channel.socket.nio.NioSocketChannel; -import java.net.InetSocketAddress; - -public class DapClient { - - public DapClient() { - Bootstrap b = new Bootstrap(); - b.group(new NioEventLoopGroup(4)); - b.channel(NioSocketChannel.class); - b.remoteAddress(new InetSocketAddress("localhost", 4711)); - b.handler(new ChannelInitializer() { - @Override - protected void initChannel(Channel c) throws Exception { - ChannelPipeline p = c.pipeline(); - p.addLast(new DapDecoder()); - p.addLast(new DapEncoder()); - p.addLast(new DapClientHandler()); - } - }); - try { - ChannelFuture cf = b.connect().sync(); - cf.channel().closeFuture().sync(); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - -} diff --git a/karate-core/src/main/java/com/intuit/karate/debug/DapClientHandler.java b/karate-core/src/main/java/com/intuit/karate/debug/DapClientHandler.java deleted file mode 100644 index 2b734dc42..000000000 --- a/karate-core/src/main/java/com/intuit/karate/debug/DapClientHandler.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * The MIT License - * - * Copyright 2022 Karate Labs Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.intuit.karate.debug; - -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.SimpleChannelInboundHandler; -import java.util.concurrent.atomic.AtomicInteger; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * - * @author peter - */ -public class DapClientHandler extends SimpleChannelInboundHandler { - - private static final Logger logger = LoggerFactory.getLogger(DapClientHandler.class); - - private static final AtomicInteger seq = new AtomicInteger(); - - @Override - public void channelActive(ChannelHandlerContext ctx) throws Exception { - DapMessage dm = DapMessage.request(seq.incrementAndGet(), "initialize"); - ctx.writeAndFlush(dm); - } - - @Override - protected void channelRead0(ChannelHandlerContext chc, DapMessage dm) throws Exception { - logger.debug("read: {}", dm); - } - -} diff --git a/karate-core/src/main/java/com/intuit/karate/debug/DapDecoder.java b/karate-core/src/main/java/com/intuit/karate/debug/DapDecoder.java deleted file mode 100644 index 2397d0b30..000000000 --- a/karate-core/src/main/java/com/intuit/karate/debug/DapDecoder.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * The MIT License - * - * Copyright 2022 Karate Labs Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.intuit.karate.debug; - -import com.intuit.karate.Json; -import io.netty.buffer.ByteBuf; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.ByteToMessageDecoder; -import io.netty.util.ByteProcessor; -import java.nio.charset.StandardCharsets; -import java.util.List; -import java.util.Map; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * - * @author pthomas3 - */ -public class DapDecoder extends ByteToMessageDecoder { - - private static final Logger logger = LoggerFactory.getLogger(DapDecoder.class); - - private int remaining; - - @Override - protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { - if (remaining > 0 && in.readableBytes() >= remaining) { - out.add(encode(in, remaining)); - remaining = 0; - } - int pos; - while ((pos = findCrLfCrLf(in)) != -1) { - int delimiterPos = pos; - while (in.getByte(--pos) != ':') { - // skip backwards - } - in.readerIndex(++pos); - CharSequence lengthString = in.readCharSequence(delimiterPos - pos, StandardCharsets.UTF_8); - int length = Integer.valueOf(lengthString.toString().trim()); - in.readerIndex(delimiterPos + 4); - if (in.readableBytes() >= length) { - out.add(encode(in, length)); - remaining = 0; - } else { - remaining = length; - } - } - } - - private static int findCrLfCrLf(ByteBuf buffer) { - int totalLength = buffer.readableBytes(); - int readerIndex = buffer.readerIndex(); - int i = buffer.forEachByte(readerIndex, totalLength, ByteProcessor.FIND_LF); - if (i > 0 && buffer.getByte(i - 1) == '\r') { - int more = readerIndex + totalLength - i; - if (more > 1 && buffer.getByte(i + 1) == '\r' && buffer.getByte(i + 2) == '\n') { - return i - 1; - } - } - return -1; - } - - private static DapMessage encode(ByteBuf in, int length) { - String msg = in.readCharSequence(length, StandardCharsets.UTF_8).toString(); - if (logger.isTraceEnabled()) { - logger.trace(">> {}", msg); - } - Map map = Json.of(msg).value(); - return new DapMessage(map); - } - -} diff --git a/karate-core/src/main/java/com/intuit/karate/debug/DapEncoder.java b/karate-core/src/main/java/com/intuit/karate/debug/DapEncoder.java deleted file mode 100644 index 4afd0c7a6..000000000 --- a/karate-core/src/main/java/com/intuit/karate/debug/DapEncoder.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * The MIT License - * - * Copyright 2022 Karate Labs Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.intuit.karate.debug; - -import io.netty.buffer.ByteBuf; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.MessageToMessageEncoder; -import java.nio.charset.StandardCharsets; -import java.util.List; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * - * @author pthomas3 - */ -public class DapEncoder extends MessageToMessageEncoder { - - private static final Logger logger = LoggerFactory.getLogger(DapEncoder.class); - - private static final byte[] CONTENT_LENGTH_COLON = "Content-Length: ".getBytes(StandardCharsets.UTF_8); - private static final byte[] CRLFCRLF = "\r\n\r\n".getBytes(StandardCharsets.UTF_8); - - @Override - protected void encode(ChannelHandlerContext ctx, DapMessage dm, List out) throws Exception { - String msg = dm.toJson(); - if (logger.isTraceEnabled()) { - logger.trace("<< {}", msg); - } - ByteBuf buf = ctx.alloc().buffer(); - byte[] bytes = msg.getBytes(StandardCharsets.UTF_8); - buf.writeBytes(CONTENT_LENGTH_COLON); - buf.writeCharSequence(bytes.length + "", StandardCharsets.UTF_8); - buf.writeBytes(CRLFCRLF); - buf.writeBytes(bytes); - out.add(buf); - } - -} diff --git a/karate-core/src/main/java/com/intuit/karate/debug/DapMessage.java b/karate-core/src/main/java/com/intuit/karate/debug/DapMessage.java deleted file mode 100644 index b6997e203..000000000 --- a/karate-core/src/main/java/com/intuit/karate/debug/DapMessage.java +++ /dev/null @@ -1,186 +0,0 @@ -/* - * The MIT License - * - * Copyright 2022 Karate Labs Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.intuit.karate.debug; - -import com.intuit.karate.JsonUtils; -import java.util.HashMap; -import java.util.Map; - -/** - * - * @author pthomas3 - */ -public class DapMessage { - - public static enum Type { - REQUEST, - RESPONSE, - EVENT - } - - public final int seq; - public final Type type; - public final String command; - public final String event; - - private Map arguments; - - private Integer requestSeq; - private Boolean success; - private String message; - - - private Map body; - - public DapMessage body(String key, Object value) { - if (body == null) { - body = new HashMap(); - } - body.put(key, value); - return this; - } - - public Map getArguments() { - return arguments; - } - - public Number getThreadId() { - return getArgument("threadId", Number.class); - } - - public T getArgument(String key, Class clazz) { - if (arguments == null) { - return null; - } - return (T) arguments.get(key); - } - - public static Type parse(String s) { - switch (s) { - case "request": - return Type.REQUEST; - case "response": - return Type.RESPONSE; - default: - return Type.EVENT; - } - } - - public static DapMessage request(int seq, String command) { - return new DapMessage(seq, Type.REQUEST, command, null); - } - - public static DapMessage event(int seq, String name) { - return new DapMessage(seq, Type.EVENT, null, name); - } - - public static DapMessage response(int seq, DapMessage req) { - DapMessage dm = new DapMessage(seq, Type.RESPONSE, req.command, null); - dm.requestSeq = req.seq; - dm.success = true; - return dm; - } - - private DapMessage(int seq, Type type, String command, String event) { - this.seq = seq; - this.type = type; - this.command = command; - this.event = event; - } - - public DapMessage(Map map) { - seq = (Integer) map.get("seq"); - type = parse((String) map.get("type")); - command = (String) map.get("command"); - arguments = (Map) map.get("arguments"); - requestSeq = (Integer) map.get("request_seq"); - success = (Boolean) map.get("success"); - message = (String) map.get("message"); - event = (String) map.get("event"); - body = (Map) map.get("body"); - } - - public String toJson() { - return JsonUtils.toJsonSafe(toMap(), false); - } - - public Map toMap() { - Map map = new HashMap(4); - map.put("seq", seq); - map.put("type", type.toString().toLowerCase()); - if (command != null) { - map.put("command", command); - } - if (arguments != null) { - map.put("arguments", arguments); - } - if (requestSeq != null) { - map.put("request_seq", requestSeq); - } - if (success != null) { - map.put("success", success); - } - if (message != null) { - map.put("message", message); - } - if (event != null) { - map.put("event", event); - } - if (body != null) { - map.put("body", body); - } - return map; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("[seq: ").append(seq); - sb.append(", type: ").append(type); - if (command != null) { - sb.append(", command: ").append(command); - } - if (arguments != null) { - sb.append(", arguments: ").append(arguments); - } - if (requestSeq != null) { - sb.append(", request_seq: ").append(requestSeq); - } - if (success != null) { - sb.append(", success: ").append(success); - } - if (message != null) { - sb.append(", message: ").append(message); - } - if (event != null) { - sb.append(", event: ").append(event); - } - if (body != null) { - sb.append(", body: ").append(body); - } - sb.append("]"); - return sb.toString(); - } - -} diff --git a/karate-core/src/main/java/com/intuit/karate/debug/DapServer.java b/karate-core/src/main/java/com/intuit/karate/debug/DapServer.java deleted file mode 100644 index 2f444ed65..000000000 --- a/karate-core/src/main/java/com/intuit/karate/debug/DapServer.java +++ /dev/null @@ -1,107 +0,0 @@ - /* - * The MIT License - * - * Copyright 2022 Karate Labs Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.intuit.karate.debug; - -import io.netty.bootstrap.ServerBootstrap; -import io.netty.channel.Channel; -import io.netty.channel.ChannelInitializer; -import io.netty.channel.ChannelPipeline; -import io.netty.channel.EventLoopGroup; -import io.netty.channel.nio.NioEventLoopGroup; -import io.netty.channel.socket.nio.NioServerSocketChannel; -import java.net.InetSocketAddress; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * - * @author pthomas3 - */ -public class DapServer { - - private static final Logger logger = LoggerFactory.getLogger(DapServer.class); - - private final EventLoopGroup bossGroup; - private final EventLoopGroup workerGroup; - private final Channel channel; - private final String host; - private final int port; - private final boolean keepAlive; - - public int getPort() { - return port; - } - - public boolean isKeepAlive() { - return keepAlive; - } - - public void waitSync() { - try { - channel.closeFuture().sync(); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - public void stop() { - logger.info("stop: shutting down"); - bossGroup.shutdownGracefully(); - workerGroup.shutdownGracefully(); - logger.info("stop: shutdown complete"); - } - - public DapServer(int requestedPort) { - this(requestedPort, true); - } - - public DapServer(int requestedPort, boolean keepAlive) { - bossGroup = new NioEventLoopGroup(1); - workerGroup = new NioEventLoopGroup(); - this.keepAlive = keepAlive; - try { - ServerBootstrap b = new ServerBootstrap(); - b.group(bossGroup, workerGroup) - .channel(NioServerSocketChannel.class) - // .handler(new LoggingHandler(getClass().getName(), LogLevel.TRACE)) - .childHandler(new ChannelInitializer() { - @Override - protected void initChannel(Channel c) { - ChannelPipeline p = c.pipeline(); - p.addLast(new DapDecoder()); - p.addLast(new DapEncoder()); - p.addLast(new DapServerHandler(DapServer.this)); - } - }); - channel = b.bind(requestedPort).sync().channel(); - InetSocketAddress isa = (InetSocketAddress) channel.localAddress(); - host = "127.0.0.1"; //isa.getHostString(); - port = isa.getPort(); - logger.info("debug server started on port: {}", port); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - -} diff --git a/karate-core/src/main/java/com/intuit/karate/debug/DapServerHandler.java b/karate-core/src/main/java/com/intuit/karate/debug/DapServerHandler.java deleted file mode 100644 index 6868e5372..000000000 --- a/karate-core/src/main/java/com/intuit/karate/debug/DapServerHandler.java +++ /dev/null @@ -1,526 +0,0 @@ -/* - * The MIT License - * - * Copyright 2022 Karate Labs Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.intuit.karate.debug; - -import com.intuit.karate.*; -import com.intuit.karate.cli.IdeMain; -import com.intuit.karate.core.Feature; -import com.intuit.karate.core.Result; -import com.intuit.karate.core.RuntimeHookFactory; -import com.intuit.karate.core.ScenarioEngine; -import com.intuit.karate.core.ScenarioRuntime; -import com.intuit.karate.core.Step; -import com.intuit.karate.core.Variable; -import static com.intuit.karate.core.Variable.Type.LIST; -import static com.intuit.karate.core.Variable.Type.MAP; -import io.netty.buffer.Unpooled; -import io.netty.channel.Channel; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.SimpleChannelInboundHandler; - -import java.io.File; -import java.nio.file.Paths; -import java.util.*; -import java.util.AbstractMap.SimpleEntry; -import java.util.Map.Entry; -import java.util.concurrent.ConcurrentHashMap; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * - * @author pthomas3 - */ -public class DapServerHandler extends SimpleChannelInboundHandler implements RuntimeHookFactory { - - private static final Logger logger = LoggerFactory.getLogger(DapServerHandler.class); - - private final DapServer server; - - private Channel channel; - private int nextSeq; - private long nextFrameId; - private long nextVariablesReference = 1000; // setting to 1000 to avoid collisions with nextFrameId - private long focusedFrameId; - private Thread runnerThread; - - private final Map BREAKPOINTS = new ConcurrentHashMap(); - protected final Map THREADS = new ConcurrentHashMap(); - protected final Map FRAMES = new ConcurrentHashMap(); - protected final Map>> FRAME_VARS = new ConcurrentHashMap(); - protected final Map> VARIABLES = new ConcurrentHashMap(); - - private List launchArgs; - private String launchCommand; - private String preStep; - - public DapServerHandler(DapServer server) { - this.server = server; - } - - private static final String TEST_CLASSES = "/test-classes/"; - private static final String CLASSES_TEST = "/classes/java/test/"; - - private static int findPos(String path) { - int pos = path.indexOf(TEST_CLASSES); - if (pos != -1) { - return pos + TEST_CLASSES.length(); - } - pos = path.indexOf(CLASSES_TEST); - if (pos != -1) { - return pos + CLASSES_TEST.length(); - } - return -1; - } - - private SourceBreakpoints lookup(String pathEnd) { - for (Entry entry : BREAKPOINTS.entrySet()) { - if (entry.getKey().endsWith(pathEnd)) { - return entry.getValue(); - } - } - return null; - } - - protected Breakpoint resolveBreakpoint(Step step, int line, ScenarioRuntime context) { - Feature feature = step.getFeature(); - File file = feature.getResource().getFile(); - if (file == null) { - return null; - } - String path = normalizePath(file.getPath()); - int pos = findPos(path); - SourceBreakpoints sb; - if (pos != -1) { - sb = lookup(path.substring(pos)); - } else { - sb = BREAKPOINTS.get(path); - } - if (sb == null) { - return null; - } - return sb.resolveBreakpoint(line, context); - } - - protected String normalizePath(String path) { - String normalizedPath = Paths.get(path).normalize().toString(); - if (FileUtils.isOsWindows() && path.matches("^[a-zA-Z]:\\\\.*")) { - // in Windows if the first character is the drive, let's capitalize it - // Windows paths are case insensitive but in the debugger it mostly comes capitalized but sometimes - // VS Studio sends the paths with the first letter lower case - normalizedPath = normalizedPath.substring(0, 1).toUpperCase() + normalizedPath.substring(1); - } - return normalizedPath; - } - - private DebugThread thread(DapMessage dm) { - Number threadId = dm.getThreadId(); - if (threadId == null) { - return null; - } - return THREADS.get(threadId.longValue()); - } - - private List> frames(Number threadId) { - if (threadId == null) { - return Collections.EMPTY_LIST; - } - DebugThread thread = THREADS.get(threadId.longValue()); - if (thread == null) { - return Collections.EMPTY_LIST; - } - List frameIds = new ArrayList(thread.stack); - Collections.reverse(frameIds); - List> list = new ArrayList(frameIds.size()); - for (Long frameId : frameIds) { - ScenarioRuntime context = FRAMES.get(frameId); - list.add(new StackFrame(frameId, context).toMap()); - } - return list; - } - - private List> variables(Long frameId) { - if (frameId == null) { - return Collections.EMPTY_LIST; - } - String parentExpression = ""; - Map vars = null; - if (FRAME_VARS.containsKey(frameId)) { - focusedFrameId = frameId; - Stack> varsStack = FRAME_VARS.get(frameId); - if (varsStack.isEmpty()) { - return Collections.EMPTY_LIST; // edge case, no variables were even created yet - } - vars = varsStack.peek(); - } else if (VARIABLES.containsKey(frameId)) { - vars = new HashMap<>(); - Entry varEntry = VARIABLES.get(frameId); - parentExpression = varEntry.getKey(); - Variable var = varEntry.getValue(); - if (var.type == LIST) { - List list = ((List) var.getValue()); - for (int i = 0; i < list.size(); i++) { - vars.put(String.format("[%s]", i), new Variable(list.get(i))); - } - } else if (var.type == MAP) { - Map map = ((Map) var.getValue()); - for (Entry entry : map.entrySet()) { - vars.put(entry.getKey(), new Variable(entry.getValue())); - } - } - } else { - return Collections.EMPTY_LIST; - } - String finalParentExpression = parentExpression; - List> list = new ArrayList(); - vars.forEach((k, v) -> { - if (v != null) { - Map map = new HashMap(); - map.put("name", k); - try { - map.put("value", v.getAsString()); - } catch (Exception e) { - logger.warn("unable to convert to string: {} - {}", k, v); - map.put("value", "(unknown)"); - } - map.put("type", v.type.name()); - // remove last dot before an array - String pathExpression = k.startsWith("[") ? finalParentExpression.replaceAll("\\.$", "") : finalParentExpression; - if (v.type == LIST || v.type == MAP) { - VARIABLES.put(++nextVariablesReference, new SimpleEntry(pathExpression + k + ".", v)); - map.put("presentationHint", "data"); - map.put("variablesReference", nextVariablesReference); - } else { - map.put("variablesReference", 0); - } - map.put("evaluateName", pathExpression + k); - list.add(map); - } - }); - Collections.sort(list, (a, b) -> ((String) a.get("name")).compareTo((String) b.get("name"))); - return list; - } - - private DapMessage event(String name) { - return DapMessage.event(++nextSeq, name); - } - - private DapMessage response(DapMessage req) { - return DapMessage.response(++nextSeq, req); - } - - @Override - protected void channelRead0(ChannelHandlerContext ctx, DapMessage dm) throws Exception { - switch (dm.type) { - case REQUEST: - handleRequest(dm, ctx); - break; - default: - logger.warn("ignoring message: {}", dm); - } - } - - private void handleRequest(DapMessage req, ChannelHandlerContext ctx) { - switch (req.command) { - case "initialize": - ctx.write(response(req) - .body("supportsConfigurationDoneRequest", true) - .body("supportsRestartRequest", true) - .body("supportsStepBack", true) - .body("supportsVariableType", true) - .body("supportsValueFormattingOptions", true) - .body("supportsClipboardContext", true)); - ctx.write(event("initialized")); - ctx.write(event("output").body("output", "debug server listening on port: " + server.getPort() + "\n")); - break; - case "setBreakpoints": - SourceBreakpoints sb = new SourceBreakpoints(req.getArguments()); - BREAKPOINTS.put(normalizePath(sb.path), sb); - logger.trace("source breakpoints: {}", sb); - ctx.write(response(req).body("breakpoints", sb.getBreakpointsAsListOfMaps())); - break; - case "launch": - // normally a single feature full path, but can be set with any valid karate.options - // for e.g. "-t @smoke -T 5 classpath:demo.feature" - launchArgs = req.getArgument("karateArgs", List.class); - launchCommand = StringUtils.trimToEmpty(req.getArgument("karateOptions", String.class)); - String feature = StringUtils.trimToNull(req.getArgument("feature", String.class)); - if (feature != null) { - if (launchArgs != null) { - launchArgs.add(feature); - } else { - launchCommand = launchCommand + " " + feature; - } - } - preStep = StringUtils.trimToNull(req.getArgument("debugPreStep", String.class)); - if (preStep != null) { - logger.debug("using pre-step: {}", preStep); - } - start(); - ctx.write(response(req)); - break; - case "threads": - List> list = new ArrayList(THREADS.size()); - THREADS.values().forEach(v -> { - Map map = new HashMap(); - map.put("id", v.id); - map.put("name", v.name); - list.add(map); - }); - ctx.write(response(req).body("threads", list)); - break; - case "stackTrace": - ctx.write(response(req).body("stackFrames", frames(req.getThreadId()))); - break; - case "configurationDone": - ctx.write(response(req)); - break; - case "scopes": - Number frameId = req.getArgument("frameId", Number.class); - Map scope = new HashMap(); - scope.put("name", "In Scope"); - scope.put("variablesReference", frameId); - scope.put("presentationHint", "locals"); - scope.put("expensive", false); - ctx.write(response(req).body("scopes", Collections.singletonList(scope))); - break; - case "variables": - Integer variablesReference = req.getArgument("variablesReference", Integer.class); - ctx.write(response(req).body("variables", variables(variablesReference.longValue()))); - break; - case "next": - thread(req).next().resume(); - ctx.write(response(req)); - break; - case "stepBack": - case "reverseContinue": // since we can't disable this button - thread(req).stepBack().resume(); - ctx.write(response(req)); - break; - case "stepIn": - thread(req).stepIn().resume(); - ctx.write(response(req)); - break; - case "stepOut": - thread(req).stepOut().resume(); - ctx.write(response(req)); - break; - case "continue": - thread(req)._continue().resume(); - ctx.write(response(req)); - break; - case "pause": - ctx.write(response(req)); - thread(req).pause(); - break; - case "evaluate": - String expression = req.getArgument("expression", String.class); - Number evalFrameId = req.getArgument("frameId", Number.class); - String reqContext = req.getArgument("context", String.class); - ScenarioRuntime evalContext = FRAMES.get(evalFrameId.longValue()); - String result; - if ("clipboard".equals(reqContext) || "hover".equals(reqContext)) { - result = evaluateVarExpression(evalContext.engine.vars, expression); - } else { - ScenarioEngine.set(evalContext.engine); - evaluatePreStep(evalContext); - Throwable engineFailedReason = evalContext.engine.getFailedReason(); - evalContext.engine.setFailedReason(null); - // TODO: candidate to evaluate several steps in a scenario fashion - Result evalResult = evalContext.evalAsStep(expression); - if (evalResult.isFailed()) { - result = "[error] " + evalResult.getError().getMessage(); - } else { - result = "[done]"; - } - evalContext.engine.setFailedReason(engineFailedReason); // reset engine failed reason to original failure status - } - ctx.write(response(req) - .body("result", result) - .body("variablesReference", 0)); // non-zero means can be requested by client - break; - case "restart": - ScenarioRuntime context = FRAMES.get(focusedFrameId); - if (context != null && context.hotReload()) { - output("[debug] hot reload successful"); - } else { - output("[debug] hot reload requested, but no steps edited"); - } - ctx.write(response(req)); - break; - case "disconnect": - boolean restart = req.getArgument("restart", Boolean.class); - if (restart) { - start(); - } else { - exit(); - } - ctx.write(response(req)); - break; - default: - logger.warn("unknown command: {}", req); - ctx.write(response(req)); - } - ctx.writeAndFlush(Unpooled.EMPTY_BUFFER); - } - - protected String evaluateVarExpression(Map vars, String expression) { - String result = ""; - try { - if (expression.contains(".")) { - String varName = expression.substring(0, expression.indexOf('.')); - String path = expression.substring(expression.indexOf('.') + 1); - Object nested = Json.of(vars.get(varName).getValue()).get(path); - result = JsonUtils.toJsonSafe(nested, true); - } else { - Variable v = vars.get(expression); - result = v.getAsPrettyString(); - } - } catch (Exception e) { - result = "[error] " + e.getMessage(); - } - return result; - } - - protected void evaluatePreStep(ScenarioRuntime context) { - if (preStep == null) { - return; - } - Result result = context.evalAsStep(preStep); - if (result.isFailed()) { - output("[debug] pre-step failed: " + preStep + " - " + result.getError().getMessage()); - } else { - output("[debug] pre-step success: " + preStep); - } - } - - @Override - public RuntimeHook create() { - return new DebugThread(Thread.currentThread(), this); - } - - private void start() { - Main options; - if (launchArgs != null) { - logger.debug("command args: {}", launchArgs); - options = Main.parseKarateArgs(launchArgs); - } else { - logger.debug("command line: {}", launchCommand); - options = IdeMain.parseIdeCommandLine(launchCommand); - } - if (runnerThread != null) { - runnerThread.interrupt(); - } - runnerThread = new Thread(() -> { - Runner.path(options.getPaths()) - .debugMode(true) - .hookFactory(this) - .hooks(options.createHooks()) - .tags(options.getTags()) - .configDir(options.getConfigDir()) - .karateEnv(options.getEnv()) - .outputHtmlReport(options.isOutputHtmlReport()) - .outputCucumberJson(options.isOutputCucumberJson()) - .outputJunitXml(options.isOutputJunitXml()) - .scenarioName(options.getName()) - .parallel(options.getThreads()); - // if we reached here, run was successful - exit(); - }); - runnerThread.start(); - } - - protected void stopEvent(long threadId, String reason, String description, List breakPointIds) { - channel.eventLoop().execute(() -> { - DapMessage message = event("stopped") - .body("reason", reason) - .body("threadId", threadId); - if (description != null) { - message.body("description", description); - } - if (breakPointIds != null) { - message.body("hitBreakpointIds", breakPointIds); - } - channel.writeAndFlush(message); - }); - } - - protected void continueEvent(long threadId) { - channel.eventLoop().execute(() -> { - DapMessage message = event("continued") - .body("threadId", threadId); - channel.writeAndFlush(message); - }); - } - - private void exit() { - channel.eventLoop().execute(() - -> channel.writeAndFlush(event("exited") - .body("exitCode", 0))); - if (server.isKeepAlive()) { - server.stop(); - // System.exit(0); - } else { - logger.debug("Disconnecting current debug session. Debug server listening on port {}", this.server.getPort()); - this.clearDebugSession(); - channel.disconnect(); - } - } - - private void clearDebugSession() { - this.BREAKPOINTS.clear(); - this.THREADS.clear(); - this.FRAMES.clear(); - this.FRAME_VARS.clear(); - this.VARIABLES.clear(); - - launchCommand = null; - preStep = null; - if (runnerThread != null && runnerThread.isAlive()) { - runnerThread.interrupt(); - } - } - - protected long nextFrameId() { - return ++nextFrameId; - } - - protected void output(String text) { - channel.eventLoop().execute(() - -> channel.writeAndFlush(event("output") - .body("output", text))); - } - - @Override - public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { - cause.printStackTrace(); - ctx.close(); - } - - @Override - public void channelActive(ChannelHandlerContext ctx) { - channel = ctx.channel(); - } - -} diff --git a/karate-core/src/main/java/com/intuit/karate/debug/DebugThread.java b/karate-core/src/main/java/com/intuit/karate/debug/DebugThread.java deleted file mode 100644 index 565568b7a..000000000 --- a/karate-core/src/main/java/com/intuit/karate/debug/DebugThread.java +++ /dev/null @@ -1,279 +0,0 @@ -/* - * The MIT License - * - * Copyright 2022 Karate Labs Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.intuit.karate.debug; - -import com.intuit.karate.LogAppender; -import com.intuit.karate.core.*; -import com.intuit.karate.RuntimeHook; -import java.util.Collections; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Stack; -import java.util.stream.Collectors; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * - * @author pthomas3 - */ -public class DebugThread implements RuntimeHook, LogAppender { - - private static final Logger logger = LoggerFactory.getLogger(DebugThread.class); - - public final long id; - public final String name; - public final Thread thread; - public final Stack stack = new Stack(); - private final Map stepModes = new HashMap(); - public final DapServerHandler handler; - - private boolean stepIn; - private boolean stepBack; - private boolean paused; - private boolean interrupted; - private boolean stopped; - private boolean errored; - - private final String appenderPrefix; - private LogAppender appender = LogAppender.NO_OP; - - public DebugThread(Thread thread, DapServerHandler handler) { - id = thread.getId(); - name = thread.getName(); - appenderPrefix = "[" + name + "] "; - this.thread = thread; - this.handler = handler; - } - - protected void pause() { - paused = true; - } - - private boolean stop(String reason) { - return stop(reason, null); - } - - private boolean stop(String reason, List breakPointIds) { - return stop(reason, null, breakPointIds); - } - - private boolean stop(String reason, String description, List breakPointIds) { - handler.stopEvent(id, reason, description, breakPointIds); - stopped = true; - synchronized (this) { - try { - wait(); - } catch (Exception e) { - logger.warn("thread error: {}", e.getMessage()); - interrupted = true; - return false; // exit all the things - } - } - handler.continueEvent(id); - // if we reached here - we have "resumed" - // the stepBack logic is a little faulty and can only be called BEFORE beforeStep() (yes 2 befores) - if (stepBack) { // don't clear flag yet ! - getContext().stepBack(); - return false; // abort and do not execute step ! - } - if (stopped) { - getContext().stepReset(); - return false; - } - return true; - } - - protected void resume() { - stopped = false; - handler.evaluatePreStep(getContext()); - for (DebugThread dt : handler.THREADS.values()) { - synchronized (dt) { - dt.notify(); - } - } - } - - @Override - public boolean beforeScenario(ScenarioRuntime context) { - long frameId = handler.nextFrameId(); - stack.push(frameId); - handler.FRAMES.put(frameId, context); - handler.FRAME_VARS.put(frameId, new Stack<>()); - if (context.caller.depth == 0) { - handler.THREADS.put(id, this); - } - appender = context.getLogAppender(); - context.logger.setAppender(this); // wrap - return true; - } - - @Override - public void afterScenario(ScenarioRuntime context) { - stack.pop(); - if (context.caller.depth == 0) { - handler.THREADS.remove(id); - } - context.logger.setAppender(appender); // unwrap - } - - @Override - public boolean beforeStep(Step step, ScenarioRuntime context) { - if (interrupted) { - return false; - } - if (paused) { - paused = false; - return stop("pause"); - } else if (errored) { - errored = false; // clear the flag else the debugger will never move past this step - if (isStepMode()) { - // allow user to skip this step even if it is broken - context.stepProceed(); - return false; - } else { - // rewind and stop so that user can re-try this step after hot-fixing it - context.stepReset(); - return false; - } - } else if (stepBack) { - stepBack = false; - return stop("step"); - } else if (stepIn) { - stepIn = false; - return stop("step"); - } else if (isStepMode()) { - return stop("step"); - } else { - int line = step.getLine(); - Breakpoint sb = handler.resolveBreakpoint(step, line, context); - if (sb != null) { - return stop("breakpoint", Collections.singletonList(sb.id)); - } else { - return true; - } - } - } - - @Override - public void afterStep(StepResult result, ScenarioRuntime context) { - if (result.getResult().isFailed()) { - String errorMessage = result.getErrorMessage(); - handler.output("*** step failed: " + errorMessage + "\n"); - stop("exception", errorMessage, null); - errored = true; - } - pushDebugFrameVariables(context); - } - - private void pushDebugFrameVariables(ScenarioRuntime context) { - Map vars = context.engine.vars.entrySet().stream() - .collect(Collectors.toMap(v -> v.getKey(), v -> v.getValue().copy(true))); - Stack> stackVars = handler.FRAME_VARS.get(stack.peek()); - if (stackVars != null) { - stackVars.push(vars); - } - } - - private void popDebugFrameVariables() { - handler.FRAME_VARS.get(stack.peek()).pop(); - } - - private ScenarioRuntime getContext() { - return handler.FRAMES.get(stack.peek()); - } - - protected DebugThread _continue() { - stepModes.clear(); - return this; - } - - protected DebugThread next() { - stepModes.put(stack.size(), true); - return this; - } - - protected DebugThread stepOut() { - int stackSize = stack.size(); - stepModes.put(stackSize, false); - if (stackSize > 1) { - stepModes.put(stackSize - 1, true); - } - return this; - } - - protected boolean isStepMode() { - Boolean stepMode = stepModes.get(stack.size()); - return stepMode == null ? false : stepMode; - } - - protected DebugThread stepIn() { - this.stepIn = true; - return this; - } - - protected DebugThread stepBack() { - popDebugFrameVariables(); - stepBack = true; - return this; - } - - public LogAppender getAppender() { - return appender; - } - - public void setAppender(LogAppender appender) { - this.appender = appender; - } - - @Override - public String getBuffer() { - return appender.getBuffer(); - } - - @Override - public String collect() { - return appender.collect(); - } - - @Override - public void append(String text) { - handler.output(appenderPrefix + text); - appender.append(text); - } - - @Override - public void close() { - - } - - @Override - public String toString() { - return "id: " + id + ", name: " + name + ", stack: " + stack; - } - -} diff --git a/karate-core/src/main/java/com/intuit/karate/debug/SourceBreakpoints.java b/karate-core/src/main/java/com/intuit/karate/debug/SourceBreakpoints.java deleted file mode 100644 index 7d6744009..000000000 --- a/karate-core/src/main/java/com/intuit/karate/debug/SourceBreakpoints.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * The MIT License - * - * Copyright 2022 Karate Labs Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.intuit.karate.debug; - -import com.intuit.karate.Json; -import com.intuit.karate.core.ScenarioRuntime; -import com.intuit.karate.core.Variable; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -/** - * - * @author pthomas3 - */ -public class SourceBreakpoints { - - public final String name; - public final String path; - public final List breakpoints; - public final boolean sourceModified; - - public Breakpoint resolveBreakpoint(int line, ScenarioRuntime context) { - if (breakpoints == null || breakpoints.isEmpty()) { - return null; - } - for (Breakpoint b : breakpoints) { - if (b.line == line) { - if (b.condition == null) { - return b; - } else { - Variable evalCondition = context.engine.evalKarateExpression(b.condition); - if (evalCondition != null && evalCondition.type != Variable.Type.BOOLEAN) { - return b; - } - if (evalCondition != null && evalCondition.isTrue()) { - return b; - } - } - } - } - return null; - } - - public SourceBreakpoints(Map map) { - Json json = Json.of(map); - name = json.get("source.name"); - path = json.get("source.path"); - List> list = json.get("breakpoints"); - breakpoints = new ArrayList(list.size()); - for (Map bm : list) { - breakpoints.add(new Breakpoint(bm)); - } - sourceModified = json.get("sourceModified"); - } - - public List getBreakpointsAsListOfMaps() { - List list = new ArrayList(breakpoints.size()); - for (Breakpoint b : breakpoints) { - list.add(b.toMap()); - } - return list; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("[name: ").append(name); - sb.append(", path: ").append(path); - sb.append(", breakpoints: ").append(breakpoints); - sb.append(", sourceModified: ").append(sourceModified); - sb.append("]"); - return sb.toString(); - } - -} diff --git a/karate-core/src/main/java/com/intuit/karate/debug/StackFrame.java b/karate-core/src/main/java/com/intuit/karate/debug/StackFrame.java deleted file mode 100644 index 891cf254e..000000000 --- a/karate-core/src/main/java/com/intuit/karate/debug/StackFrame.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * The MIT License - * - * Copyright 2022 Karate Labs Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.intuit.karate.debug; - -import com.intuit.karate.core.Scenario; -import com.intuit.karate.core.Step; -import com.intuit.karate.core.ScenarioRuntime; -import java.io.File; -import java.util.HashMap; -import java.util.Map; - -/** - * - * @author pthomas3 - */ -public class StackFrame { - - private final long id; - private final int line; - private final int column = 0; - private final String name; - private final Map source = new HashMap(); - - public StackFrame(long frameId, ScenarioRuntime context) { - this.id = frameId; - Step step = context.getCurrentStep(); - line = step.getLine(); - Scenario scenario = context.scenario; - name = scenario.getRefId(); - File file = scenario.getFeature().getResource().getFile(); - source.put("name", file.getName()); - source.put("path", file.getPath()); - source.put("sourceReference", 0); //if not zero, source can be requested by client via a message - } - - public Map toMap() { - Map map = new HashMap(); - map.put("id", id); - map.put("line", line); - map.put("column", column); - map.put("name", name); - map.put("source", source); - return map; - } - -} diff --git a/karate-core/src/test/java/com/intuit/karate/debug/DapClientRunner.java b/karate-core/src/test/java/com/intuit/karate/debug/DapClientRunner.java deleted file mode 100644 index 9ab7b8788..000000000 --- a/karate-core/src/test/java/com/intuit/karate/debug/DapClientRunner.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.intuit.karate.debug; - -import org.junit.jupiter.api.Test; - -class DapClientRunner { - - @Test - void testClient() { - new DapClient(); - } - -} diff --git a/karate-core/src/test/java/com/intuit/karate/debug/DapServerRunner.java b/karate-core/src/test/java/com/intuit/karate/debug/DapServerRunner.java deleted file mode 100644 index ad2e373f9..000000000 --- a/karate-core/src/test/java/com/intuit/karate/debug/DapServerRunner.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.intuit.karate.debug; - -import org.junit.jupiter.api.Test; - -/** - * mvn exec:java -Dexec.mainClass="com.intuit.karate.cli.Main" -Dexec.args="-d 4711" -Dexec.classpathScope=test - * @author pthomas3 - */ -class DapServerRunner { - - @Test - public void testDap() { - DapServer server = new DapServer(4711); - server.waitSync(); - } - -}