From e50b15135598cf0574c977ed2daac2e59bca92fa Mon Sep 17 00:00:00 2001 From: Vladimir Turov Date: Wed, 20 Oct 2021 17:27:41 +0300 Subject: [PATCH 01/55] Update dependencies + move to java 11 --- build.gradle | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/build.gradle b/build.gradle index 931179a4..4b35ea0e 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ plugins { group 'org.hyperskill' version '8.2' -sourceCompatibility = 1.8 +sourceCompatibility = 11 repositories { mavenCentral() @@ -20,12 +20,12 @@ dependencies { // implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" - api 'org.assertj:assertj-swing-junit:3.9.2' - api 'org.apache.httpcomponents:httpclient:4.5.9' - api 'com.google.code.gson:gson:2.8.5' + api 'org.assertj:assertj-swing-junit:3.17.1' + api 'org.apache.httpcomponents:httpclient:4.5.13' + api 'com.google.code.gson:gson:2.8.8' - compileOnly 'org.projectlombok:lombok:1.18.16' - annotationProcessor 'org.projectlombok:lombok:1.18.16' + compileOnly 'org.projectlombok:lombok:1.18.22' + annotationProcessor 'org.projectlombok:lombok:1.18.22' } compileJava.options.encoding = 'UTF-8' @@ -42,7 +42,7 @@ tasks.withType(JavaCompile) { // } wrapper { - gradleVersion = '6.6.1' + gradleVersion = '7.2' } task resolveDependencies { @@ -63,11 +63,11 @@ task resolveDependencies { } task createFatJar(type: Jar) { + duplicatesStrategy = DuplicatesStrategy.EXCLUDE manifest { attributes 'Main-Class': 'org.hyperskill.hstest.stage.StageTest' } from { - configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } } with jar From 92ff2ffc6720dae05ac76c50b29a6aa2f1c2ef2e Mon Sep 17 00:00:00 2001 From: Vladimir Turov Date: Thu, 21 Oct 2021 22:26:36 +0300 Subject: [PATCH 02/55] Use more rigid system to catch output by installing handlers --- .../hyperskill/hstest/common/FileUtils.java | 57 +++++++++++++++- .../hstest/dynamic/SystemHandler.java | 12 ++++ .../hstest/dynamic/input/InputHandler.java | 23 ++----- .../hstest/dynamic/input/InputMock.java | 53 ++++++++++----- .../hstest/dynamic/output/OutputHandler.java | 22 +++++- .../hstest/dynamic/output/OutputMock.java | 68 ++++++++++++++++--- .../hyperskill/hstest/stage/StageTest.java | 45 ++++++++++-- .../hstest/testing/ExecutionOptions.java | 2 +- .../hyperskill/hstest/testing/TestRun.java | 7 ++ .../testing/execution/MainMethodExecutor.java | 14 ++-- .../testing/execution/ProcessExecutor.java | 21 ++++++ .../testing/execution/ProgramExecutor.java | 14 ++-- .../testing/execution/process/GoExecutor.java | 9 +++ .../execution/process/JavascriptExecutor.java | 9 +++ .../execution/process/PythonExecutor.java | 9 +++ ...er.java => AsyncDynamicTestingRunner.java} | 19 +++++- src/test/java/dynamic/TestOutputHandler.java | 2 + ...GettingOutputWhileProgramInBackground.java | 8 +-- src/test/java/outcomes/lib/OutputInTests.java | 39 +++++++++++ .../java/outcomes/lib/OutputInTests2.java | 49 +++++++++++++ 20 files changed, 406 insertions(+), 76 deletions(-) create mode 100644 src/main/java/org/hyperskill/hstest/testing/execution/ProcessExecutor.java create mode 100644 src/main/java/org/hyperskill/hstest/testing/execution/process/GoExecutor.java create mode 100644 src/main/java/org/hyperskill/hstest/testing/execution/process/JavascriptExecutor.java create mode 100644 src/main/java/org/hyperskill/hstest/testing/execution/process/PythonExecutor.java rename src/main/java/org/hyperskill/hstest/testing/runner/{AsyncMainMethodRunner.java => AsyncDynamicTestingRunner.java} (85%) create mode 100644 src/test/java/outcomes/lib/OutputInTests.java create mode 100644 src/test/java/outcomes/lib/OutputInTests2.java diff --git a/src/main/java/org/hyperskill/hstest/common/FileUtils.java b/src/main/java/org/hyperskill/hstest/common/FileUtils.java index b56365a4..a07dd57f 100644 --- a/src/main/java/org/hyperskill/hstest/common/FileUtils.java +++ b/src/main/java/org/hyperskill/hstest/common/FileUtils.java @@ -1,15 +1,21 @@ package org.hyperskill.hstest.common; +import lombok.Data; + import java.io.File; import java.io.IOException; -import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.HashSet; +import java.util.Iterator; +import java.util.List; import java.util.Map; +import java.util.NoSuchElementException; import java.util.Set; +import static java.util.stream.Collectors.toList; + public final class FileUtils { private FileUtils() { } @@ -74,11 +80,56 @@ public static String readFile(String name) { } Path path = Paths.get(name); try { - return new String(Files.readAllBytes(path), StandardCharsets.UTF_8); - //return Files.readString(path); <- Java 11 + return Files.readString(path); } catch (IOException ignored) { return null; } } + @Data + public static class Folder { + final File folder; + final List dirs; + final List files; + } + + public static Iterable walkUserFiles(String folder) throws IOException { + var currFolder = new File(folder).getAbsolutePath(); + var testFolder = new File(currFolder, "test").getAbsolutePath(); + + Iterator walk = Files.walk(Paths.get(currFolder)) + .filter(Files::isDirectory) + .filter(path -> !path.startsWith(Paths.get(testFolder))) + .iterator(); + + return () -> new Iterator<>() { + @Override + public boolean hasNext() { + return walk.hasNext(); + } + + @Override + public Folder next() { + Path next = walk.next(); + + if (next == null) { + throw new NoSuchElementException(); + } + + File[] children = next.toFile().listFiles(); + + if (children == null) { + throw new NoSuchElementException(); + } + + List listChildren = List.of(children); + + List dirs = listChildren.stream().filter(File::isDirectory).collect(toList()); + List files = listChildren.stream().filter(File::isFile).collect(toList()); + + return new FileUtils.Folder(next.toFile(), dirs, files); + } + }; + } + } diff --git a/src/main/java/org/hyperskill/hstest/dynamic/SystemHandler.java b/src/main/java/org/hyperskill/hstest/dynamic/SystemHandler.java index 11824eb2..68f4f81c 100644 --- a/src/main/java/org/hyperskill/hstest/dynamic/SystemHandler.java +++ b/src/main/java/org/hyperskill/hstest/dynamic/SystemHandler.java @@ -4,9 +4,11 @@ import org.hyperskill.hstest.dynamic.output.OutputHandler; import org.hyperskill.hstest.dynamic.security.TestingSecurityManager; import org.hyperskill.hstest.exception.outcomes.ErrorWithFeedback; +import org.hyperskill.hstest.testing.execution.ProgramExecutor; import java.util.Locale; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Supplier; import static java.lang.System.getSecurityManager; import static org.hyperskill.hstest.common.JavaUtils.isSecurityManagerAllowed; @@ -97,4 +99,14 @@ private static void unlockSystemForTesting() { } lockerThread = null; } + + public static void installHandler(ProgramExecutor program, Supplier condition) { + InputHandler.installInputHandler(program, condition); + OutputHandler.installOutputHandler(program, condition); + } + + public static void uninstallHandler(ProgramExecutor program) { + InputHandler.uninstallInputHandler(program); + OutputHandler.uninstallOutputHandler(program); + } } diff --git a/src/main/java/org/hyperskill/hstest/dynamic/input/InputHandler.java b/src/main/java/org/hyperskill/hstest/dynamic/input/InputHandler.java index dd268277..8190b25f 100644 --- a/src/main/java/org/hyperskill/hstest/dynamic/input/InputHandler.java +++ b/src/main/java/org/hyperskill/hstest/dynamic/input/InputHandler.java @@ -1,8 +1,9 @@ package org.hyperskill.hstest.dynamic.input; +import org.hyperskill.hstest.testing.execution.ProgramExecutor; + import java.io.InputStream; -import java.util.LinkedList; -import java.util.List; +import java.util.function.Supplier; public final class InputHandler { @@ -19,22 +20,12 @@ public static void revertInput() { System.setIn(realIn); } - public static void setDynamicInputFunc(ThreadGroup group, DynamicTestFunction func) { - mockIn.setDynamicInputFunction(group, func); + public static void installInputHandler(ProgramExecutor program, Supplier condition) { + mockIn.installInputHandler(program, condition); } - @Deprecated - public static void setInput(String input) { - mockIn.provideText(input); + public static void uninstallInputHandler(ProgramExecutor program) { + mockIn.uninstallInputHandler(program); } - @Deprecated - public static void setInputFuncs(List inputFuncs) { - List newFuncs = new LinkedList<>(); - for (DynamicInputFunction func : inputFuncs) { - newFuncs.add(new DynamicInputFunction( - func.getTriggerCount(), func.getInputFunction())); - } - mockIn.setTexts(newFuncs); - } } diff --git a/src/main/java/org/hyperskill/hstest/dynamic/input/InputMock.java b/src/main/java/org/hyperskill/hstest/dynamic/input/InputMock.java index c5e68ebd..a6861cf9 100644 --- a/src/main/java/org/hyperskill/hstest/dynamic/input/InputMock.java +++ b/src/main/java/org/hyperskill/hstest/dynamic/input/InputMock.java @@ -1,35 +1,52 @@ package org.hyperskill.hstest.dynamic.input; -import org.hyperskill.hstest.dynamic.security.TestingSecurityManager; +import lombok.Data; +import org.hyperskill.hstest.dynamic.security.ExitException; import org.hyperskill.hstest.exception.outcomes.UnexpectedError; +import org.hyperskill.hstest.stage.StageTest; +import org.hyperskill.hstest.testing.execution.ProgramExecutor; import java.io.InputStream; import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; import java.util.Map; +import java.util.function.Supplier; public class InputMock extends InputStream { - private final Map handlers = new HashMap<>(); + @Data + private static class ConditionalInputHandler { + final Supplier condition; + final DynamicInputHandler handler; + } + + private final Map handlers = new HashMap<>(); - @Deprecated - void provideText(String text) { - List texts = new LinkedList<>(); - texts.add(new DynamicInputFunction(1, out -> text)); - setTexts(texts); + void installInputHandler(ProgramExecutor program, Supplier condition) { + if (handlers.containsKey(program)) { + throw new UnexpectedError("Cannot install input handler from the same program twice"); + } + handlers.put(program, new ConditionalInputHandler( + condition, + new DynamicInputHandler(program::requestInput) + )); } - @Deprecated - void setTexts(List texts) { - // inputTextFuncs = texts; - // TODO setDynamicInputFunction(DynamicInput.toDynamicInput()); + void uninstallInputHandler(ProgramExecutor program) { + if (!handlers.containsKey(program)) { + throw new UnexpectedError("Cannot uninstall input handler that doesn't exist"); + } + handlers.remove(program); } - void setDynamicInputFunction(ThreadGroup group, DynamicTestFunction func) { - if (handlers.containsKey(group)) { - throw new UnexpectedError("Cannot change dynamic input function"); + private DynamicInputHandler getInputHandler() { + for (var handler : handlers.values()) { + if (handler.condition.get()) { + return handler.handler; + } } - handlers.put(group, new DynamicInputHandler(func)); + + StageTest.getCurrTestRun().setErrorInTest( + new UnexpectedError("Cannot find input handler to read data")); + throw new ExitException(0); } @Override @@ -60,6 +77,6 @@ public int read(byte[] b, int off, int len) { @Override public int read() { - return handlers.get(TestingSecurityManager.getTestingGroup()).ejectChar(); + return getInputHandler().ejectChar(); } } diff --git a/src/main/java/org/hyperskill/hstest/dynamic/output/OutputHandler.java b/src/main/java/org/hyperskill/hstest/dynamic/output/OutputHandler.java index 91079f26..0b273b7e 100644 --- a/src/main/java/org/hyperskill/hstest/dynamic/output/OutputHandler.java +++ b/src/main/java/org/hyperskill/hstest/dynamic/output/OutputHandler.java @@ -2,8 +2,10 @@ import org.hyperskill.hstest.stage.StageTest; import org.hyperskill.hstest.testing.TestRun; +import org.hyperskill.hstest.testing.execution.ProgramExecutor; import java.io.PrintStream; +import java.util.function.Supplier; import static org.hyperskill.hstest.common.Utils.cleanText; @@ -17,6 +19,10 @@ private OutputHandler() { } private static final OutputMock MOCK_OUT = new OutputMock(REAL_OUT); private static final OutputMock MOCK_ERR = new OutputMock(REAL_ERR); + public static void print(String text) { + getRealOut().println(text); + } + public static PrintStream getRealOut() { return MOCK_OUT.getOriginal(); } @@ -53,8 +59,8 @@ public static String getDynamicOutput() { return cleanText(MOCK_OUT.getDynamic()); } - public static String getPartialOutput(ThreadGroup group) { - return cleanText(MOCK_OUT.getPartial(group)); + public static String getPartialOutput(ProgramExecutor program) { + return cleanText(MOCK_OUT.getPartial(program)); } public static void injectInput(String input) { @@ -64,4 +70,16 @@ public static void injectInput(String input) { } MOCK_OUT.injectInput(input); } + + public static void installOutputHandler(ProgramExecutor program, Supplier condition) { + MOCK_OUT.installOutputHandler(program, condition); + MOCK_ERR.installOutputHandler(program, condition); + } + + public static void uninstallOutputHandler(ProgramExecutor program) { + MOCK_OUT.uninstallOutputHandler(program); + MOCK_ERR.uninstallOutputHandler(program); + } + + } diff --git a/src/main/java/org/hyperskill/hstest/dynamic/output/OutputMock.java b/src/main/java/org/hyperskill/hstest/dynamic/output/OutputMock.java index 928c06fa..67c1df9f 100644 --- a/src/main/java/org/hyperskill/hstest/dynamic/output/OutputMock.java +++ b/src/main/java/org/hyperskill/hstest/dynamic/output/OutputMock.java @@ -1,17 +1,27 @@ package org.hyperskill.hstest.dynamic.output; +import lombok.Data; import lombok.Getter; -import org.hyperskill.hstest.dynamic.security.TestingSecurityManager; +import org.hyperskill.hstest.exception.outcomes.UnexpectedError; +import org.hyperskill.hstest.testing.execution.ProgramExecutor; import java.io.ByteArrayOutputStream; import java.io.OutputStream; import java.io.PrintStream; import java.util.HashMap; import java.util.Map; +import java.util.function.Supplier; +import static org.hyperskill.hstest.dynamic.output.ColoredOutput.BLUE; +import static org.hyperskill.hstest.dynamic.output.ColoredOutput.RESET; import static org.hyperskill.hstest.testing.ExecutionOptions.ignoreStdout; public class OutputMock extends OutputStream { + @Data + private static class ConditionalOutput { + final Supplier condition; + final ByteArrayOutputStream output = new ByteArrayOutputStream(); + } // original stream is used to actually see // the test in the console and nothing else @@ -27,7 +37,11 @@ public class OutputMock extends OutputStream { // partial stream is used to collect output between // dynamic input calls in SystemInMock - private final Map partial = new HashMap<>(); + private final Map partial = new HashMap<>(); + + // test output is used to print text inside tests + // this text will be printed in blue to distinguish it from user's program text + private final ByteArrayOutputStream testOutput = new ByteArrayOutputStream(); OutputMock(PrintStream originalStream) { this.original = new PrintStream(new OutputStream() { @@ -42,15 +56,23 @@ public void write(int b) { @Override public synchronized void write(int b) { + var partialHandler = getPartialHandler(); + + if (partialHandler == null) { + testOutput.write(b); + if (b == '\n') { + original.print(BLUE + testOutput + RESET); + testOutput.reset(); + } + return; + } + original.write(b); cloned.write(b); dynamic.write(b); + partialHandler.write(b); InfiniteLoopDetector.write(b); - - ThreadGroup currGroup = TestingSecurityManager.getTestingGroup(); - partial.putIfAbsent(currGroup, new ByteArrayOutputStream()); - partial.get(currGroup).write(b); } @Override @@ -73,7 +95,9 @@ public synchronized void injectInput(String input) { public void reset() { cloned.reset(); dynamic.reset(); - partial.clear(); + for (var value : partial.values()) { + value.getOutput().reset(); + } InfiniteLoopDetector.reset(); } @@ -85,12 +109,34 @@ public String getDynamic() { return dynamic.toString(); } - public synchronized String getPartial(ThreadGroup group) { - ByteArrayOutputStream s = - partial.getOrDefault(group, new ByteArrayOutputStream()); - + public synchronized String getPartial(ProgramExecutor program) { + ByteArrayOutputStream s = partial.get(program).output; String output = s.toString(); s.reset(); return output; } + + void installOutputHandler(ProgramExecutor program, Supplier condition) { + if (partial.containsKey(program)) { + throw new UnexpectedError("Cannot install output handler from the same program twice"); + } + partial.put(program, new ConditionalOutput(condition)); + } + + void uninstallOutputHandler(ProgramExecutor program) { + if (!partial.containsKey(program)) { + throw new UnexpectedError("Cannot uninstall output handler that doesn't exist"); + } + partial.remove(program); + } + + private ByteArrayOutputStream getPartialHandler() { + for (var handler : partial.values()) { + if (handler.condition.get()) { + return handler.output; + } + } + return null; + } + } diff --git a/src/main/java/org/hyperskill/hstest/stage/StageTest.java b/src/main/java/org/hyperskill/hstest/stage/StageTest.java index c0ec3212..08ae0689 100644 --- a/src/main/java/org/hyperskill/hstest/stage/StageTest.java +++ b/src/main/java/org/hyperskill/hstest/stage/StageTest.java @@ -11,17 +11,22 @@ import org.hyperskill.hstest.testcase.CheckResult; import org.hyperskill.hstest.testcase.TestCase; import org.hyperskill.hstest.testing.TestRun; -import org.hyperskill.hstest.testing.runner.AsyncMainMethodRunner; +import org.hyperskill.hstest.testing.execution.process.GoExecutor; +import org.hyperskill.hstest.testing.execution.process.JavascriptExecutor; +import org.hyperskill.hstest.testing.execution.process.PythonExecutor; +import org.hyperskill.hstest.testing.runner.AsyncDynamicTestingRunner; import org.hyperskill.hstest.testing.runner.TestRunner; import org.junit.Test; import org.junit.runner.JUnitCore; import org.junit.runner.Result; +import java.io.IOException; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; +import static org.hyperskill.hstest.common.FileUtils.walkUserFiles; import static org.hyperskill.hstest.dynamic.input.DynamicTesting.searchDynamicTests; import static org.hyperskill.hstest.dynamic.output.ColoredOutput.RED_BOLD; import static org.hyperskill.hstest.dynamic.output.ColoredOutput.RESET; @@ -29,8 +34,9 @@ public abstract class StageTest { - protected TestRunner runner = new AsyncMainMethodRunner(); + protected TestRunner runner = new AsyncDynamicTestingRunner(); protected AttachType attach = null; + protected String source = null; @Getter private static TestRun currTestRun; private final String sourceName; @@ -43,6 +49,10 @@ public StageTest() { } public StageTest(String sourceName) { + if (source != null) { + sourceName = source; + } + Package currPackage = getClass().getPackage(); String strPackage = ""; @@ -62,7 +72,29 @@ public StageTest(Class testedClass) { this(testedClass.getName()); } - private List initTests() { + private TestRunner initRunner() throws IOException { + for (var folder : walkUserFiles(".")) { + for (var file : folder.getFiles()) { + if (file.getName().endsWith(".go")) { + return new AsyncDynamicTestingRunner(GoExecutor.class); + } + if (file.getName().endsWith(".js")) { + return new AsyncDynamicTestingRunner(JavascriptExecutor.class); + } + if (file.getName().endsWith(".py")) { + return new AsyncDynamicTestingRunner(PythonExecutor.class); + } + } + } + + return new AsyncDynamicTestingRunner(); + } + + private List initTests() throws IOException { + if (runner == null) { + runner = initRunner(); + } + List testRuns = new ArrayList<>(); List> testCases = new ArrayList<>(generate()); testCases.addAll(searchDynamicTests(this)); @@ -89,9 +121,7 @@ private List initTests() { private void printTestNum(int num) { String totalTests = num == currTestGlobal ? "" : " (" + currTestGlobal + ")"; - OutputHandler.getRealOut().println( - RED_BOLD + "\nStart test " + num + totalTests + RESET - ); + OutputHandler.print(RED_BOLD + "\nStart test " + num + totalTests + RESET); } @Test @@ -155,6 +185,9 @@ public final void start() { fail(failText); } finally { currTestRun = null; + runner = null; + attach = null; + source = null; try { SystemHandler.tearDownSystem(); } catch (Throwable ignored) { } diff --git a/src/main/java/org/hyperskill/hstest/testing/ExecutionOptions.java b/src/main/java/org/hyperskill/hstest/testing/ExecutionOptions.java index 9b1addfa..2070bba5 100644 --- a/src/main/java/org/hyperskill/hstest/testing/ExecutionOptions.java +++ b/src/main/java/org/hyperskill/hstest/testing/ExecutionOptions.java @@ -45,7 +45,7 @@ private ExecutionOptions() { } /** * Enables SecurityManager even though it's not recommended being used in Java 17 - * In Java 8-16 it still will be used, on Java 17 it's not unless this flag is set to true. + * In Java 8-17 it still will be used, on Java 18 it's not unless this flag is set to true. * Use "-DforceSecurityManager=true" to set this flag. */ public static boolean forceSecurityManager = Boolean.getBoolean("forceSecurityManager"); diff --git a/src/main/java/org/hyperskill/hstest/testing/TestRun.java b/src/main/java/org/hyperskill/hstest/testing/TestRun.java index 7bfa045f..d32842fd 100644 --- a/src/main/java/org/hyperskill/hstest/testing/TestRun.java +++ b/src/main/java/org/hyperskill/hstest/testing/TestRun.java @@ -1,6 +1,7 @@ package org.hyperskill.hstest.testing; import lombok.Getter; +import org.hyperskill.hstest.dynamic.SystemHandler; import org.hyperskill.hstest.dynamic.output.OutputHandler; import org.hyperskill.hstest.exception.outcomes.ExceptionWithFeedback; import org.hyperskill.hstest.exception.outcomes.TestPassed; @@ -66,6 +67,12 @@ public void stopTestedPrograms() { } } + public void invalidateHandlers() { + for (var testedProgram : testedPrograms) { + SystemHandler.uninstallHandler(testedProgram.getProgramExecutor()); + } + } + public final void setUp() { testRunner.setUp(testCase); } diff --git a/src/main/java/org/hyperskill/hstest/testing/execution/MainMethodExecutor.java b/src/main/java/org/hyperskill/hstest/testing/execution/MainMethodExecutor.java index 7ab50f2e..52e0ac64 100644 --- a/src/main/java/org/hyperskill/hstest/testing/execution/MainMethodExecutor.java +++ b/src/main/java/org/hyperskill/hstest/testing/execution/MainMethodExecutor.java @@ -2,9 +2,9 @@ import org.hyperskill.hstest.common.ReflectionUtils; import org.hyperskill.hstest.dynamic.DynamicClassLoader; -import org.hyperskill.hstest.dynamic.input.InputHandler; -import org.hyperskill.hstest.dynamic.output.OutputHandler; +import org.hyperskill.hstest.dynamic.SystemHandler; import org.hyperskill.hstest.dynamic.security.ExitException; +import org.hyperskill.hstest.dynamic.security.TestingSecurityManager; import org.hyperskill.hstest.exception.outcomes.ErrorWithFeedback; import org.hyperskill.hstest.exception.outcomes.ExceptionWithFeedback; import org.hyperskill.hstest.exception.outcomes.UnexpectedError; @@ -95,8 +95,6 @@ private void initByNothing(String userPackage) { } private void initByNothing(String userPackage, boolean tryEmptyPackage) { - // TODO use javap and regex "public static( final)? void main\(java\.lang\.String(\[\]|\.\.\.)\)" - List> classesWithMainMethod = ReflectionUtils .getAllClassesFromPackage(userPackage) .stream() @@ -194,7 +192,8 @@ private void invokeMain(String[] args) { @Override protected void launch(String... args) { initMethod(); - InputHandler.setDynamicInputFunc(group, this::requestInput); + SystemHandler.installHandler(this, + () -> TestingSecurityManager.getTestingGroup() == group); executor = newDaemonThreadPool(1, group); task = executor.submit(() -> invokeMain(args)); } @@ -215,11 +214,6 @@ protected void terminate() { } } - @Override - public String getOutput() { - return OutputHandler.getPartialOutput(group); - } - @Override public String toString() { return runClass.getSimpleName(); diff --git a/src/main/java/org/hyperskill/hstest/testing/execution/ProcessExecutor.java b/src/main/java/org/hyperskill/hstest/testing/execution/ProcessExecutor.java new file mode 100644 index 00000000..19165e36 --- /dev/null +++ b/src/main/java/org/hyperskill/hstest/testing/execution/ProcessExecutor.java @@ -0,0 +1,21 @@ +package org.hyperskill.hstest.testing.execution; + +public class ProcessExecutor extends ProgramExecutor { + protected ProcessExecutor(String source) { + } + + @Override + protected void launch(String... args) { + + } + + @Override + protected void terminate() { + + } + + @Override + public String toString() { + return null; + } +} diff --git a/src/main/java/org/hyperskill/hstest/testing/execution/ProgramExecutor.java b/src/main/java/org/hyperskill/hstest/testing/execution/ProgramExecutor.java index 5498acce..358de1e8 100644 --- a/src/main/java/org/hyperskill/hstest/testing/execution/ProgramExecutor.java +++ b/src/main/java/org/hyperskill/hstest/testing/execution/ProgramExecutor.java @@ -1,5 +1,6 @@ package org.hyperskill.hstest.testing.execution; +import org.hyperskill.hstest.dynamic.output.OutputHandler; import org.hyperskill.hstest.exception.outcomes.ErrorWithFeedback; import org.hyperskill.hstest.exception.outcomes.UnexpectedError; import org.hyperskill.hstest.exception.testing.TestedProgramFinishedEarly; @@ -7,6 +8,7 @@ import org.hyperskill.hstest.stage.StageTest; import org.hyperskill.hstest.testing.StateMachine; +import static org.hyperskill.hstest.testing.execution.ProgramExecutor.ProgramState.COMPILATION_ERROR; import static org.hyperskill.hstest.testing.execution.ProgramExecutor.ProgramState.EXCEPTION_THROWN; import static org.hyperskill.hstest.testing.execution.ProgramExecutor.ProgramState.FINISHED; import static org.hyperskill.hstest.testing.execution.ProgramExecutor.ProgramState.NOT_STARTED; @@ -21,17 +23,18 @@ public abstract class ProgramExecutor { * States that tested program can be in * Initial state in NOT_STARTED, * State just before running the program is READY - * End state is either EXCEPTION_THROWN or FINISHED + * End state is EXCEPTION_THROWN, FINISHED or COMPILATION_ERROR * WAITING means the tested program waits for the input * RUNNING means the tested program is currently running */ protected enum ProgramState { - NOT_STARTED, WAITING, RUNNING, EXCEPTION_THROWN, FINISHED + NOT_STARTED, WAITING, RUNNING, EXCEPTION_THROWN, FINISHED, COMPILATION_ERROR } protected final StateMachine machine = new StateMachine<>(NOT_STARTED); { + machine.addTransition(NOT_STARTED, COMPILATION_ERROR); machine.addTransition(NOT_STARTED, RUNNING); machine.addTransition(WAITING, RUNNING); @@ -47,7 +50,10 @@ protected enum ProgramState { protected abstract void launch(String... args); protected abstract void terminate(); - public abstract String getOutput(); + + public final String getOutput() { + return OutputHandler.getPartialOutput(this); + } public final String start(String... args) { if (!machine.inState(NOT_STARTED)) { @@ -113,7 +119,7 @@ private String getExecutionOutput() { return returnOutputAfterExecution ? getOutput() : ""; } - protected final String requestInput() { + public final String requestInput() { if (noMoreInput) { return null; } diff --git a/src/main/java/org/hyperskill/hstest/testing/execution/process/GoExecutor.java b/src/main/java/org/hyperskill/hstest/testing/execution/process/GoExecutor.java new file mode 100644 index 00000000..d75bd14f --- /dev/null +++ b/src/main/java/org/hyperskill/hstest/testing/execution/process/GoExecutor.java @@ -0,0 +1,9 @@ +package org.hyperskill.hstest.testing.execution.process; + +import org.hyperskill.hstest.testing.execution.ProcessExecutor; + +public class GoExecutor extends ProcessExecutor { + protected GoExecutor(String source) { + super(source); + } +} diff --git a/src/main/java/org/hyperskill/hstest/testing/execution/process/JavascriptExecutor.java b/src/main/java/org/hyperskill/hstest/testing/execution/process/JavascriptExecutor.java new file mode 100644 index 00000000..81e7d759 --- /dev/null +++ b/src/main/java/org/hyperskill/hstest/testing/execution/process/JavascriptExecutor.java @@ -0,0 +1,9 @@ +package org.hyperskill.hstest.testing.execution.process; + +import org.hyperskill.hstest.testing.execution.ProcessExecutor; + +public class JavascriptExecutor extends ProcessExecutor { + protected JavascriptExecutor(String source) { + super(source); + } +} diff --git a/src/main/java/org/hyperskill/hstest/testing/execution/process/PythonExecutor.java b/src/main/java/org/hyperskill/hstest/testing/execution/process/PythonExecutor.java new file mode 100644 index 00000000..3d431603 --- /dev/null +++ b/src/main/java/org/hyperskill/hstest/testing/execution/process/PythonExecutor.java @@ -0,0 +1,9 @@ +package org.hyperskill.hstest.testing.execution.process; + +import org.hyperskill.hstest.testing.execution.ProcessExecutor; + +public class PythonExecutor extends ProcessExecutor { + protected PythonExecutor(String source) { + super(source); + } +} diff --git a/src/main/java/org/hyperskill/hstest/testing/runner/AsyncMainMethodRunner.java b/src/main/java/org/hyperskill/hstest/testing/runner/AsyncDynamicTestingRunner.java similarity index 85% rename from src/main/java/org/hyperskill/hstest/testing/runner/AsyncMainMethodRunner.java rename to src/main/java/org/hyperskill/hstest/testing/runner/AsyncDynamicTestingRunner.java index f5560838..5340b525 100644 --- a/src/main/java/org/hyperskill/hstest/testing/runner/AsyncMainMethodRunner.java +++ b/src/main/java/org/hyperskill/hstest/testing/runner/AsyncDynamicTestingRunner.java @@ -10,6 +10,8 @@ import org.hyperskill.hstest.testcase.CheckResult; import org.hyperskill.hstest.testcase.TestCase; import org.hyperskill.hstest.testing.TestRun; +import org.hyperskill.hstest.testing.execution.MainMethodExecutor; +import org.hyperskill.hstest.testing.execution.ProgramExecutor; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; @@ -19,7 +21,21 @@ import static org.hyperskill.hstest.testing.ExecutionOptions.debugMode; -public class AsyncMainMethodRunner implements TestRunner { +public class AsyncDynamicTestingRunner implements TestRunner { + + protected Class executor; + + public AsyncDynamicTestingRunner() { + this(MainMethodExecutor.class); + } + + public AsyncDynamicTestingRunner(Class executor) { + this.executor = executor; + } + + Class getExecutor() { + return executor; + } private CheckResult runMain(TestRun testRun) { TestCase testCase = testRun.getTestCase(); @@ -57,6 +73,7 @@ private CheckResult runMain(TestRun testRun) { } catch (Throwable ex) { testRun.setErrorInTest(ex); } finally { + testRun.invalidateHandlers(); executorService.shutdownNow(); } diff --git a/src/test/java/dynamic/TestOutputHandler.java b/src/test/java/dynamic/TestOutputHandler.java index e6012ecf..9985b1c6 100644 --- a/src/test/java/dynamic/TestOutputHandler.java +++ b/src/test/java/dynamic/TestOutputHandler.java @@ -13,10 +13,12 @@ public class TestOutputHandler { @Before public void setUp() { SystemHandler.setUp(); + OutputHandler.installOutputHandler(null, () -> true); } @After public void tearDown() { + OutputHandler.uninstallOutputHandler(null); SystemHandler.tearDownSystem(); } diff --git a/src/test/java/outcomes/dynamic_method/TestGettingOutputWhileProgramInBackground.java b/src/test/java/outcomes/dynamic_method/TestGettingOutputWhileProgramInBackground.java index 817f524c..7376aa5c 100644 --- a/src/test/java/outcomes/dynamic_method/TestGettingOutputWhileProgramInBackground.java +++ b/src/test/java/outcomes/dynamic_method/TestGettingOutputWhileProgramInBackground.java @@ -26,26 +26,26 @@ CheckResult test() { String out = main.getOutput(); if (!out.equals("")) { - return CheckResult.wrong(""); + return CheckResult.wrong("1) Real out (length " + out.length() + "):\n" + out); } sleep(75); out = main.getOutput(); if (!out.equals("Test\n")) { - return CheckResult.wrong(""); + return CheckResult.wrong("2) Real out (length " + out.length() + "):\n" + out); } sleep(100); out = main.getOutput(); if (!out.equals("Test\nTest\n")) { - return CheckResult.wrong(""); + return CheckResult.wrong("3) Real out (length " + out.length() + "):\n" + out); } main.stop(); if (!main.isFinished()) { - return CheckResult.wrong(""); + return CheckResult.wrong("Main is not finished"); } return CheckResult.correct(); diff --git a/src/test/java/outcomes/lib/OutputInTests.java b/src/test/java/outcomes/lib/OutputInTests.java new file mode 100644 index 00000000..58b64847 --- /dev/null +++ b/src/test/java/outcomes/lib/OutputInTests.java @@ -0,0 +1,39 @@ +package outcomes.lib; + +import org.hyperskill.hstest.dynamic.DynamicTest; +import org.hyperskill.hstest.stage.StageTest; +import org.hyperskill.hstest.testcase.CheckResult; +import org.hyperskill.hstest.testing.TestedProgram; + +import java.util.Scanner; + +class OutputInTestsMain { + public static void main(String[] args) { + System.out.println("123"); + new Scanner(System.in).nextLine(); + System.out.println("456"); + } +} + +public class OutputInTests extends StageTest { + + @DynamicTest + CheckResult test() { + var pr = new TestedProgram(OutputInTestsMain.class); + var out = pr.start(); + + if (!out.equals("123\n")) { + return CheckResult.wrong(""); + } + + System.out.println("Test output"); + out = pr.execute("789"); + + if (!out.equals("456\n")) { + return CheckResult.wrong(""); + } + + return CheckResult.correct(); + } + +} diff --git a/src/test/java/outcomes/lib/OutputInTests2.java b/src/test/java/outcomes/lib/OutputInTests2.java new file mode 100644 index 00000000..2b4ed076 --- /dev/null +++ b/src/test/java/outcomes/lib/OutputInTests2.java @@ -0,0 +1,49 @@ +package outcomes.lib; + +import org.hyperskill.hstest.dynamic.DynamicTest; +import org.hyperskill.hstest.testcase.CheckResult; +import org.hyperskill.hstest.testing.TestedProgram; +import outcomes.base.ContainsMessage; +import outcomes.base.UserErrorTest; + +import java.util.Scanner; + +class OutputInTests2Main { + public static void main(String[] args) { + System.out.println("123"); + new Scanner(System.in).nextLine(); + System.out.println("456"); + } +} + +public class OutputInTests2 extends UserErrorTest { + + @ContainsMessage + String msg = + "Wrong answer in test #1\n" + + "\n" + + "Please find below the output of your program during this failed test.\n" + + "Note that the '>' character indicates the beginning of the input line.\n" + + "\n" + + "---\n" + + "\n" + + "123\n" + + "> 789\n" + + "456"; + + @DynamicTest + CheckResult test() { + var pr = new TestedProgram(OutputInTestsMain.class); + var out = pr.start(); + + if (!out.equals("123\n")) { + return CheckResult.wrong(""); + } + + System.out.println("Test output"); + pr.execute("789"); + + return CheckResult.wrong(""); + } + +} From 6c81288cc66786da168ad68cbbb25e2504327967 Mon Sep 17 00:00:00 2001 From: Vladimir Turov Date: Thu, 21 Oct 2021 22:32:41 +0300 Subject: [PATCH 03/55] Add 2 more tests on stderr --- .../java/outcomes/lib/OutputErrInTests.java | 39 +++++++++++++++ .../java/outcomes/lib/OutputErrInTests2.java | 49 +++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 src/test/java/outcomes/lib/OutputErrInTests.java create mode 100644 src/test/java/outcomes/lib/OutputErrInTests2.java diff --git a/src/test/java/outcomes/lib/OutputErrInTests.java b/src/test/java/outcomes/lib/OutputErrInTests.java new file mode 100644 index 00000000..7c36e8d4 --- /dev/null +++ b/src/test/java/outcomes/lib/OutputErrInTests.java @@ -0,0 +1,39 @@ +package outcomes.lib; + +import org.hyperskill.hstest.dynamic.DynamicTest; +import org.hyperskill.hstest.stage.StageTest; +import org.hyperskill.hstest.testcase.CheckResult; +import org.hyperskill.hstest.testing.TestedProgram; + +import java.util.Scanner; + +class OutputErrInTestsMain { + public static void main(String[] args) { + System.out.println("123"); + new Scanner(System.in).nextLine(); + System.out.println("456"); + } +} + +public class OutputErrInTests extends StageTest { + + @DynamicTest + CheckResult test() { + var pr = new TestedProgram(OutputInTestsMain.class); + var out = pr.start(); + + if (!out.equals("123\n")) { + return CheckResult.wrong(""); + } + + System.err.println("Test output"); + out = pr.execute("789"); + + if (!out.equals("456\n")) { + return CheckResult.wrong(""); + } + + return CheckResult.correct(); + } + +} diff --git a/src/test/java/outcomes/lib/OutputErrInTests2.java b/src/test/java/outcomes/lib/OutputErrInTests2.java new file mode 100644 index 00000000..2a88fd1b --- /dev/null +++ b/src/test/java/outcomes/lib/OutputErrInTests2.java @@ -0,0 +1,49 @@ +package outcomes.lib; + +import org.hyperskill.hstest.dynamic.DynamicTest; +import org.hyperskill.hstest.testcase.CheckResult; +import org.hyperskill.hstest.testing.TestedProgram; +import outcomes.base.ContainsMessage; +import outcomes.base.UserErrorTest; + +import java.util.Scanner; + +class OutputErrInTests2Main { + public static void main(String[] args) { + System.out.println("123"); + new Scanner(System.in).nextLine(); + System.out.println("456"); + } +} + +public class OutputErrInTests2 extends UserErrorTest { + + @ContainsMessage + String msg = + "Wrong answer in test #1\n" + + "\n" + + "Please find below the output of your program during this failed test.\n" + + "Note that the '>' character indicates the beginning of the input line.\n" + + "\n" + + "---\n" + + "\n" + + "123\n" + + "> 789\n" + + "456"; + + @DynamicTest + CheckResult test() { + var pr = new TestedProgram(OutputInTestsMain.class); + var out = pr.start(); + + if (!out.equals("123\n")) { + return CheckResult.wrong(""); + } + + System.err.println("Test output"); + pr.execute("789"); + + return CheckResult.wrong(""); + } + +} From e83239212bda9914f1237c098d85af89f02bba76 Mon Sep 17 00:00:00 2001 From: Vladimir Turov Date: Fri, 22 Oct 2021 17:25:37 +0300 Subject: [PATCH 04/55] Use UTF-8 as default charset --- .../hstest/dynamic/SystemHandler.java | 21 +++++-------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/hyperskill/hstest/dynamic/SystemHandler.java b/src/main/java/org/hyperskill/hstest/dynamic/SystemHandler.java index 68f4f81c..cb2ce5be 100644 --- a/src/main/java/org/hyperskill/hstest/dynamic/SystemHandler.java +++ b/src/main/java/org/hyperskill/hstest/dynamic/SystemHandler.java @@ -23,10 +23,10 @@ private SystemHandler() { } private static SecurityManager oldSecurityManager; private static Locale oldLocale; private static String oldLineSeparator; - // private static String oldUserDir; + private static String oldDefaultCharset; private static final String separatorProperty = "line.separator"; - // private static final String userDirProperty = "user.dir"; + private static final String defaultCharsetProperty = "file.encoding"; public static void setUp() { lockSystemForTesting(); @@ -48,18 +48,8 @@ public static void setUp() { oldLineSeparator = System.getProperty(separatorProperty); System.setProperty(separatorProperty, "\n"); - /* - oldUserDir = System.getProperty(userDirProperty); - File dir = new File(oldUserDir); - if (dir.getName().equals("task")) { - // EduTools when testing sets user dir to subproject, - // but when the user is running their code user dir is set to root dir - // Since testing should be consistent with running the code we should - // revert back user dir to the root dir. - dir = dir.getParentFile().getParentFile(); - System.setProperty(userDirProperty, dir.getAbsolutePath()); - } - */ + oldDefaultCharset = System.getProperty(defaultCharsetProperty); + System.setProperty(defaultCharsetProperty, "UTF-8"); } public static void tearDownSystem() { @@ -76,8 +66,7 @@ public static void tearDownSystem() { Locale.setDefault(oldLocale); System.setProperty(separatorProperty, oldLineSeparator); - - // System.setProperty(userDirProperty, oldUserDir); + System.setProperty(defaultCharsetProperty, oldDefaultCharset); } private static void lockSystemForTesting() { From 5015c07d746a203ad43ea3e90cbf8b34c43ba5cb Mon Sep 17 00:00:00 2001 From: Vladimir Turov Date: Sat, 23 Oct 2021 01:35:53 +0300 Subject: [PATCH 05/55] Implement process handling --- .../hyperskill/hstest/common/FileUtils.java | 13 + .../hstest/dynamic/SystemHandler.java | 4 +- .../hstest/dynamic/input/InputHandler.java | 5 + .../hstest/dynamic/input/InputMock.java | 18 + .../exception/outcomes/CompilationError.java | 13 + .../outcomes/CompilationErrorOutcome.java | 15 + .../hyperskill/hstest/outcomes/Outcome.java | 4 + .../hstest/testing/ProcessWrapper.java | 326 ++++++++++++++++++ .../hstest/testing/TestedProgram.java | 8 + .../testing/execution/ProcessExecutor.java | 168 ++++++++- .../testing/execution/ProgramExecutor.java | 12 + .../testing/execution/process/GoExecutor.java | 10 +- .../execution/process/JavaExecutor.java | 18 + .../execution/process/JavascriptExecutor.java | 12 +- .../execution/process/PythonExecutor.java | 12 +- .../execution/runnable/RunnableFile.java | 11 + 16 files changed, 637 insertions(+), 12 deletions(-) create mode 100644 src/main/java/org/hyperskill/hstest/exception/outcomes/CompilationError.java create mode 100644 src/main/java/org/hyperskill/hstest/outcomes/CompilationErrorOutcome.java create mode 100644 src/main/java/org/hyperskill/hstest/testing/ProcessWrapper.java create mode 100644 src/main/java/org/hyperskill/hstest/testing/execution/process/JavaExecutor.java create mode 100644 src/main/java/org/hyperskill/hstest/testing/execution/runnable/RunnableFile.java diff --git a/src/main/java/org/hyperskill/hstest/common/FileUtils.java b/src/main/java/org/hyperskill/hstest/common/FileUtils.java index a07dd57f..789a06d6 100644 --- a/src/main/java/org/hyperskill/hstest/common/FileUtils.java +++ b/src/main/java/org/hyperskill/hstest/common/FileUtils.java @@ -1,6 +1,7 @@ package org.hyperskill.hstest.common; import lombok.Data; +import org.hyperskill.hstest.dynamic.SystemHandler; import java.io.File; import java.io.IOException; @@ -132,4 +133,16 @@ public Folder next() { }; } + public static String cwd() { + return new File(System.getProperty(SystemHandler.separatorProperty)).getAbsolutePath(); + } + + public static void chdir(String folder) { + chdir(new File(folder)); + } + + public static void chdir(File folder) { + System.setProperty(SystemHandler.separatorProperty, folder.getAbsolutePath()); + } + } diff --git a/src/main/java/org/hyperskill/hstest/dynamic/SystemHandler.java b/src/main/java/org/hyperskill/hstest/dynamic/SystemHandler.java index cb2ce5be..3d407b94 100644 --- a/src/main/java/org/hyperskill/hstest/dynamic/SystemHandler.java +++ b/src/main/java/org/hyperskill/hstest/dynamic/SystemHandler.java @@ -25,8 +25,8 @@ private SystemHandler() { } private static String oldLineSeparator; private static String oldDefaultCharset; - private static final String separatorProperty = "line.separator"; - private static final String defaultCharsetProperty = "file.encoding"; + public static final String separatorProperty = "line.separator"; + public static final String defaultCharsetProperty = "file.encoding"; public static void setUp() { lockSystemForTesting(); diff --git a/src/main/java/org/hyperskill/hstest/dynamic/input/InputHandler.java b/src/main/java/org/hyperskill/hstest/dynamic/input/InputHandler.java index 8190b25f..770094e3 100644 --- a/src/main/java/org/hyperskill/hstest/dynamic/input/InputHandler.java +++ b/src/main/java/org/hyperskill/hstest/dynamic/input/InputHandler.java @@ -2,6 +2,7 @@ import org.hyperskill.hstest.testing.execution.ProgramExecutor; +import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.util.function.Supplier; @@ -20,6 +21,10 @@ public static void revertInput() { System.setIn(realIn); } + public static ByteArrayOutputStream readline() { + return mockIn.readline(); + } + public static void installInputHandler(ProgramExecutor program, Supplier condition) { mockIn.installInputHandler(program, condition); } diff --git a/src/main/java/org/hyperskill/hstest/dynamic/input/InputMock.java b/src/main/java/org/hyperskill/hstest/dynamic/input/InputMock.java index a6861cf9..f437c4ad 100644 --- a/src/main/java/org/hyperskill/hstest/dynamic/input/InputMock.java +++ b/src/main/java/org/hyperskill/hstest/dynamic/input/InputMock.java @@ -6,6 +6,7 @@ import org.hyperskill.hstest.stage.StageTest; import org.hyperskill.hstest.testing.execution.ProgramExecutor; +import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.util.HashMap; import java.util.Map; @@ -79,4 +80,21 @@ public int read(byte[] b, int off, int len) { public int read() { return getInputHandler().ejectChar(); } + + public ByteArrayOutputStream readline() { + var result = new ByteArrayOutputStream(); + + while (true) { + int c = read(); + if (c == -1) { + break; + } + result.write(c); + if (c == '\n') { + break; + } + } + + return result; + } } diff --git a/src/main/java/org/hyperskill/hstest/exception/outcomes/CompilationError.java b/src/main/java/org/hyperskill/hstest/exception/outcomes/CompilationError.java new file mode 100644 index 00000000..3a6a051c --- /dev/null +++ b/src/main/java/org/hyperskill/hstest/exception/outcomes/CompilationError.java @@ -0,0 +1,13 @@ +package org.hyperskill.hstest.exception.outcomes; + +public class CompilationError extends OutcomeError { + private final String errorText; + + public CompilationError(String errorText) { + this.errorText = errorText; + } + + public String getErrorText() { + return errorText; + } +} diff --git a/src/main/java/org/hyperskill/hstest/outcomes/CompilationErrorOutcome.java b/src/main/java/org/hyperskill/hstest/outcomes/CompilationErrorOutcome.java new file mode 100644 index 00000000..60d428a4 --- /dev/null +++ b/src/main/java/org/hyperskill/hstest/outcomes/CompilationErrorOutcome.java @@ -0,0 +1,15 @@ +package org.hyperskill.hstest.outcomes; + +import org.hyperskill.hstest.exception.outcomes.CompilationError; + +public class CompilationErrorOutcome extends Outcome { + public CompilationErrorOutcome(CompilationError ex) { + testNumber = -1; + errorText = ex.getErrorText(); + } + + @Override + protected String getType() { + return "Compilation error"; + } +} diff --git a/src/main/java/org/hyperskill/hstest/outcomes/Outcome.java b/src/main/java/org/hyperskill/hstest/outcomes/Outcome.java index 4d514ebb..63058bae 100644 --- a/src/main/java/org/hyperskill/hstest/outcomes/Outcome.java +++ b/src/main/java/org/hyperskill/hstest/outcomes/Outcome.java @@ -1,6 +1,7 @@ package org.hyperskill.hstest.outcomes; import org.hyperskill.hstest.dynamic.output.OutputHandler; +import org.hyperskill.hstest.exception.outcomes.CompilationError; import org.hyperskill.hstest.exception.outcomes.ErrorWithFeedback; import org.hyperskill.hstest.exception.outcomes.ExceptionWithFeedback; import org.hyperskill.hstest.exception.outcomes.PresentationError; @@ -156,6 +157,9 @@ public static Outcome getOutcome(Throwable ex, int currTest) { } else if (ex instanceof ExceptionWithFeedback) { return new ExceptionOutcome(currTest, (ExceptionWithFeedback) ex); + } else if (ex instanceof CompilationError) { + return new CompilationErrorOutcome((CompilationError) ex); + } else if (ex instanceof ErrorWithFeedback || ex instanceof FileSystemException || ex instanceof TimeLimitException diff --git a/src/main/java/org/hyperskill/hstest/testing/ProcessWrapper.java b/src/main/java/org/hyperskill/hstest/testing/ProcessWrapper.java new file mode 100644 index 00000000..ec943684 --- /dev/null +++ b/src/main/java/org/hyperskill/hstest/testing/ProcessWrapper.java @@ -0,0 +1,326 @@ +package org.hyperskill.hstest.testing; + +import lombok.Getter; +import lombok.Setter; +import org.hyperskill.hstest.dynamic.security.ExitException; +import org.hyperskill.hstest.exception.outcomes.UnexpectedError; +import org.hyperskill.hstest.stage.StageTest; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.hyperskill.hstest.common.Utils.sleep; + +public class ProcessWrapper { + + private Process process; + private final String[] args; + private final String command; + @Getter private ThreadGroup group; + + private final ByteArrayOutputStream stdout = new ByteArrayOutputStream(); + private final ByteArrayOutputStream stderr = new ByteArrayOutputStream(); + private OutputStream stdin; + + private boolean alive = true; + private final AtomicInteger pipesWatching = new AtomicInteger(0); + private boolean terminated = false; + + private final List cpuLoadHistory = new LinkedList<>(); + private final int cpuLoadHistoryMax = 10; + + private final List outputDiffHistory = new LinkedList<>(); + private final int outputDiffHistoryMax = 3; + + @Getter @Setter boolean checkEarlyFinish = false; + @Getter @Setter boolean registerOutput = true; + + public ProcessWrapper(List args) { + this(args.toArray(String[]::new)); + } + + public ProcessWrapper(String... args) { + this(null, args); + } + + public ProcessWrapper(ThreadGroup group, List args) { + this(group, args.toArray(String[]::new)); + } + + public ProcessWrapper(ThreadGroup group, String... args) { + this.args = args; + this.group = group; + command = String.join(" ", args); + } + + public String getStdout() { + return stdout.toString(); + } + + public String getStderr() { + return stderr.toString(); + } + + public void provideInput(ByteArrayOutputStream input) { + try { + stdin.write(input.toByteArray()); + } catch (IOException e) { + String command = String.join(" ", args); + StageTest.getCurrTestRun().setErrorInTest( + new UnexpectedError("Can't provide input to the process\n" + command)); + } + } + + public ProcessWrapper start() { + if (process != null) { + throw new UnexpectedError( + "Cannot start the same process twice\n" + command); + } + + try { + process = new ProcessBuilder(args) + .directory(new File(System.getProperty("user.dir")).getAbsoluteFile()) + .start(); + + stdin = process.getOutputStream(); + + } catch (Exception ex) { + throw new UnexpectedError("Cannot start process\n" + command, ex); + } + + if (group == null) { + group = new ThreadGroup(args[0]); + } + + Thread checkCpuLoad = new Thread(group, this::checkCpuLoad); + checkCpuLoad.setDaemon(true); + checkCpuLoad.start(); + + Thread checkOutput = new Thread(group, this::checkOutput); + checkOutput.setDaemon(true); + checkOutput.start(); + + Thread checkStdout = new Thread(group, this::checkStdout); + checkStdout.setDaemon(true); + checkStdout.start(); + + Thread checkStderr = new Thread(group, this::checkStderr); + checkStderr.setDaemon(true); + checkStderr.start(); + + return this; + } + + public void checkAlive() { + if (alive && !process.isAlive()) { + alive = false; + } + } + + private void checkPipe(InputStream readPipe, + OutputStream writePipe, + ByteArrayOutputStream copyWritePipe) { + + pipesWatching.incrementAndGet(); + + while (true) { + + int newOutput; + try { + newOutput = readPipe.read(); + } catch (IOException ex) { + if (isFinished(false)) { + break; + } + continue; + } + + if (newOutput == -1) { + pipesWatching.decrementAndGet(); + + if (pipesWatching.get() == 0) { + alive = false; + terminate(); + } + + break; + } + + try { + if (registerOutput) { + writePipe.write(newOutput); + } + } catch (IOException ex) { + StageTest.getCurrTestRun().setErrorInTest( + new UnexpectedError("IOException writing to stdout\n" + command, ex)); + alive = false; + terminate(); + break; + } catch (ExitException ex) { + alive = false; + terminate(); + break; + } + + copyWritePipe.write(newOutput); + } + } + + private void checkStdout() { + checkPipe(process.getInputStream(), System.out, stdout); + } + + private void checkStderr() { + checkPipe(process.getErrorStream(), System.err, stderr); + } + + private void checkCpuLoad() { + long oldCpuTime = 0; + while (alive) { + var duration = process.info().totalCpuDuration(); + + if (duration.isEmpty()) { + waitOutput(); + alive = false; + break; + } + + long currCpuTime = duration.get().getSeconds() * 1_000_000_000 + duration.get().getNano(); + long currCpuLoad = currCpuTime - oldCpuTime; + oldCpuTime = currCpuTime; + + cpuLoadHistory.add(currCpuLoad); + if (cpuLoadHistory.size() > cpuLoadHistoryMax) { + cpuLoadHistory.remove(0); + } + + sleep(10); + checkAlive(); + } + } + + private void checkOutput() { + int oldOutputSize = stdout.size(); + + while (alive) { + int currOutputSize = stdout.size(); + int diff = currOutputSize - oldOutputSize; + oldOutputSize = currOutputSize; + + outputDiffHistory.add(diff); + if (outputDiffHistory.size() > outputDiffHistoryMax) { + outputDiffHistory.remove(0); + } + + sleep(10); + checkAlive(); + } + } + + public boolean isWaitingInput() { + boolean programNotLoadingProcessor = + cpuLoadHistory.size() >= cpuLoadHistoryMax + && cpuLoadHistory.stream().mapToLong(e -> e).sum() < 1; + + boolean programNotPrintingAnything = + outputDiffHistory.size() >= outputDiffHistoryMax + && outputDiffHistory.stream().mapToInt(e -> e).sum() == 0; + + return programNotLoadingProcessor && programNotPrintingAnything; + } + + public void registerInputRequest() { + if (!isWaitingInput()) { + throw new UnexpectedError("Program is not waiting for the input\n" + command); + } + cpuLoadHistory.clear(); + } + + public boolean isFinished() { + return isFinished(true); + } + + public boolean isFinished(boolean needWaitOutput) { + if (!checkEarlyFinish) { + return !alive; + } + + if (!alive) { + return true; + } + + if (!process.isAlive()) { + alive = false; + } + + if (!alive && needWaitOutput) { + waitOutput(); + } + + return !alive; + } + + public synchronized void terminate() { + if (terminated) { + return; + } + + waitOutput(); + + alive = false; + + process.descendants().forEach(pr -> { + if (!pr.destroy()) { + pr.destroyForcibly(); + } + }); + + var pr = process.toHandle(); + if (!pr.destroy()) { + pr.destroyForcibly(); + } + + terminated = true; + } + + private void waitOutput() { + int iterations = 50; + int sleepTime = 50; + + int oldStdoutSize = stdout.size(); + int oldStderrSize = stderr.size(); + + while (iterations != 0) { + sleep(sleepTime); + + int currStdoutSize = stdout.size(); + int currStderrSize = stderr.size(); + + if (currStdoutSize == oldStdoutSize && currStderrSize == oldStderrSize) { + break; + } + + oldStdoutSize = currStdoutSize; + oldStderrSize = currStderrSize; + iterations--; + } + } + + public void waitFor() { + while (!isFinished()) { + sleep(10); + } + waitOutput(); + } + + public boolean isErrorHappened() { + return !alive && stderr.size() > 0 + && !process.isAlive() && process.exitValue() != 0; + } +} diff --git a/src/main/java/org/hyperskill/hstest/testing/TestedProgram.java b/src/main/java/org/hyperskill/hstest/testing/TestedProgram.java index 7865fa12..8c15d685 100644 --- a/src/main/java/org/hyperskill/hstest/testing/TestedProgram.java +++ b/src/main/java/org/hyperskill/hstest/testing/TestedProgram.java @@ -154,6 +154,14 @@ public void stopInput() { programExecutor.stopInput(); } + /** + * Returns false if no more input will be consumed by the program. + * Otherwise, returns true. + */ + public boolean isInputAllowed() { + return programExecutor.isInputAllowed(); + } + /** * @return true if tested program waits for the input. Would be useful * for the tested program that is executed in the background. diff --git a/src/main/java/org/hyperskill/hstest/testing/execution/ProcessExecutor.java b/src/main/java/org/hyperskill/hstest/testing/execution/ProcessExecutor.java index 19165e36..66d5d3d3 100644 --- a/src/main/java/org/hyperskill/hstest/testing/execution/ProcessExecutor.java +++ b/src/main/java/org/hyperskill/hstest/testing/execution/ProcessExecutor.java @@ -1,21 +1,179 @@ package org.hyperskill.hstest.testing.execution; -public class ProcessExecutor extends ProgramExecutor { - protected ProcessExecutor(String source) { +import org.hyperskill.hstest.common.FileUtils; +import org.hyperskill.hstest.dynamic.SystemHandler; +import org.hyperskill.hstest.dynamic.input.InputHandler; +import org.hyperskill.hstest.dynamic.security.ExitException; +import org.hyperskill.hstest.dynamic.security.TestingSecurityManager; +import org.hyperskill.hstest.exception.outcomes.CompilationError; +import org.hyperskill.hstest.exception.outcomes.ExceptionWithFeedback; +import org.hyperskill.hstest.stage.StageTest; +import org.hyperskill.hstest.testing.ProcessWrapper; +import org.hyperskill.hstest.testing.execution.runnable.RunnableFile; + +import java.util.List; + +import static org.hyperskill.hstest.common.Utils.sleep; +import static org.hyperskill.hstest.testing.execution.ProgramExecutor.ProgramState.COMPILATION_ERROR; +import static org.hyperskill.hstest.testing.execution.ProgramExecutor.ProgramState.EXCEPTION_THROWN; +import static org.hyperskill.hstest.testing.execution.ProgramExecutor.ProgramState.FINISHED; +import static org.hyperskill.hstest.testing.execution.ProgramExecutor.ProgramState.RUNNING; + +public abstract class ProcessExecutor extends ProgramExecutor { + + private static boolean compiled = false; + + private ProcessWrapper process = null; + private Thread thread = null; + private ThreadGroup group; + + private boolean continueExecuting = true; + private final RunnableFile runnable; + + protected ProcessExecutor(RunnableFile runnable) { + this.runnable = runnable; + } + + protected List compilationCommand() { + return List.of(); + } + + protected String filterCompilationError(String error) { + return error; + } + + abstract protected List executionCommand(List args); + + protected void cleanup() { + + } + + private boolean compileProgram() { + if (compiled) { + return true; + } + + var command = compilationCommand(); + + if (command.size() == 0) { + return true; + } + + process = new ProcessWrapper(command); + process.setRegisterOutput(false); + process.start(); + process.waitFor(); + + if (process.isErrorHappened()) { + String errorText = filterCompilationError(process.getStderr()); + + StageTest.getCurrTestRun().setErrorInTest(new CompilationError(errorText)); + machine.setState(COMPILATION_ERROR); + return false; + } + + return true; + } + + private void handleProcess(List args) { + String oldWorkingDirectory = FileUtils.cwd(); + + try { + FileUtils.chdir(runnable.getFolder()); + + if (!compileProgram()) { + return; + } + + compiled = true; + + var command = executionCommand(args); + + machine.setState(RUNNING); + process = new ProcessWrapper(group, command).start(); + + while (continueExecuting) { + sleep(1); + + if (process.isFinished()) { + break; + } + + boolean isInputAllowed = isInputAllowed(); + boolean isWaitingInput = process.isWaitingInput(); + + if (isInputAllowed && isWaitingInput) { + process.registerInputRequest(); + + try { + var nextInput = InputHandler.readline(); + process.provideInput(nextInput); + } catch (ExitException ex) { + stopInput(); + } + } + } + + process.terminate(); + + var isErrorHappened = process.isErrorHappened(); + + if (StageTest.getCurrTestRun().getErrorInTest() != null) { + machine.setState(EXCEPTION_THROWN); + + } else if (isErrorHappened) { + StageTest.getCurrTestRun().setErrorInTest( + new ExceptionWithFeedback(process.getStderr(), null)); + machine.setState(EXCEPTION_THROWN); + + } else { + machine.setState(FINISHED); + } + + } finally { + FileUtils.chdir(oldWorkingDirectory); + } } @Override - protected void launch(String... args) { + protected final void launch(String... args) { + group = new ThreadGroup(this.toString()); + SystemHandler.installHandler(this, () -> + TestingSecurityManager.getTestingGroup() == group); + + thread = new Thread(group, () -> handleProcess(List.of(args))); + thread.setDaemon(true); + thread.start(); } @Override - protected void terminate() { + protected final void terminate() { + continueExecuting = false; + process.terminate(); + while (!isFinished()) { + if (isWaitingInput()) { + machine.setState(RUNNING); + } + sleep(1); + } + } + + @Override + public final void tearDown() { + String oldWorkingDirectory = FileUtils.cwd(); + FileUtils.chdir(runnable.getFolder()); + + try { + cleanup(); + } catch (Throwable ignored) { } + compiled = false; + FileUtils.chdir(oldWorkingDirectory); } @Override public String toString() { - return null; + return runnable.getFile().getName(); } } diff --git a/src/main/java/org/hyperskill/hstest/testing/execution/ProgramExecutor.java b/src/main/java/org/hyperskill/hstest/testing/execution/ProgramExecutor.java index 358de1e8..9c8d1733 100644 --- a/src/main/java/org/hyperskill/hstest/testing/execution/ProgramExecutor.java +++ b/src/main/java/org/hyperskill/hstest/testing/execution/ProgramExecutor.java @@ -166,6 +166,14 @@ public final void stopInput() { } } + /** + * Returns false if no more input will be consumed by the program. + * Otherwise, returns true. + */ + public final boolean isInputAllowed() { + return !noMoreInput; + } + /** * @return true if tested program waits for the input. Would be useful * for the tested program that is executed in the background. @@ -207,6 +215,10 @@ public boolean isInBackground() { return inBackground; } + public void tearDown() { + + } + @Override public abstract String toString(); } diff --git a/src/main/java/org/hyperskill/hstest/testing/execution/process/GoExecutor.java b/src/main/java/org/hyperskill/hstest/testing/execution/process/GoExecutor.java index d75bd14f..a863970f 100644 --- a/src/main/java/org/hyperskill/hstest/testing/execution/process/GoExecutor.java +++ b/src/main/java/org/hyperskill/hstest/testing/execution/process/GoExecutor.java @@ -1,9 +1,17 @@ package org.hyperskill.hstest.testing.execution.process; import org.hyperskill.hstest.testing.execution.ProcessExecutor; +import org.hyperskill.hstest.testing.execution.runnable.RunnableFile; + +import java.util.List; public class GoExecutor extends ProcessExecutor { - protected GoExecutor(String source) { + protected GoExecutor(RunnableFile source) { super(source); } + + @Override + protected List executionCommand(List args) { + return null; + } } diff --git a/src/main/java/org/hyperskill/hstest/testing/execution/process/JavaExecutor.java b/src/main/java/org/hyperskill/hstest/testing/execution/process/JavaExecutor.java new file mode 100644 index 00000000..97b52cd8 --- /dev/null +++ b/src/main/java/org/hyperskill/hstest/testing/execution/process/JavaExecutor.java @@ -0,0 +1,18 @@ +package org.hyperskill.hstest.testing.execution.process; + +import org.hyperskill.hstest.testing.execution.ProcessExecutor; +import org.hyperskill.hstest.testing.execution.runnable.RunnableFile; + +import java.util.List; + +public class JavaExecutor extends ProcessExecutor { + + protected JavaExecutor(RunnableFile runnable) { + super(runnable); + } + + @Override + protected List executionCommand(List args) { + return null; + } +} diff --git a/src/main/java/org/hyperskill/hstest/testing/execution/process/JavascriptExecutor.java b/src/main/java/org/hyperskill/hstest/testing/execution/process/JavascriptExecutor.java index 81e7d759..2451b096 100644 --- a/src/main/java/org/hyperskill/hstest/testing/execution/process/JavascriptExecutor.java +++ b/src/main/java/org/hyperskill/hstest/testing/execution/process/JavascriptExecutor.java @@ -1,9 +1,17 @@ package org.hyperskill.hstest.testing.execution.process; import org.hyperskill.hstest.testing.execution.ProcessExecutor; +import org.hyperskill.hstest.testing.execution.runnable.RunnableFile; + +import java.util.List; public class JavascriptExecutor extends ProcessExecutor { - protected JavascriptExecutor(String source) { - super(source); + protected JavascriptExecutor(RunnableFile runnable) { + super(runnable); + } + + @Override + protected List executionCommand(List args) { + return null; } } diff --git a/src/main/java/org/hyperskill/hstest/testing/execution/process/PythonExecutor.java b/src/main/java/org/hyperskill/hstest/testing/execution/process/PythonExecutor.java index 3d431603..ca8097a9 100644 --- a/src/main/java/org/hyperskill/hstest/testing/execution/process/PythonExecutor.java +++ b/src/main/java/org/hyperskill/hstest/testing/execution/process/PythonExecutor.java @@ -1,9 +1,17 @@ package org.hyperskill.hstest.testing.execution.process; import org.hyperskill.hstest.testing.execution.ProcessExecutor; +import org.hyperskill.hstest.testing.execution.runnable.RunnableFile; + +import java.util.List; public class PythonExecutor extends ProcessExecutor { - protected PythonExecutor(String source) { - super(source); + protected PythonExecutor(RunnableFile runnable) { + super(runnable); + } + + @Override + protected List executionCommand(List args) { + return null; } } diff --git a/src/main/java/org/hyperskill/hstest/testing/execution/runnable/RunnableFile.java b/src/main/java/org/hyperskill/hstest/testing/execution/runnable/RunnableFile.java new file mode 100644 index 00000000..b0179bd5 --- /dev/null +++ b/src/main/java/org/hyperskill/hstest/testing/execution/runnable/RunnableFile.java @@ -0,0 +1,11 @@ +package org.hyperskill.hstest.testing.execution.runnable; + +import lombok.Data; + +import java.io.File; + +@Data +public class RunnableFile { + final File folder; + final File file; +} From 4e614c30cae3c988715d827b0e45b31a14b4303b Mon Sep 17 00:00:00 2001 From: Vladimir Turov Date: Sat, 23 Oct 2021 01:52:02 +0300 Subject: [PATCH 06/55] Adjust input detection --- .../java/org/hyperskill/hstest/testing/ProcessWrapper.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/hyperskill/hstest/testing/ProcessWrapper.java b/src/main/java/org/hyperskill/hstest/testing/ProcessWrapper.java index ec943684..54a2221e 100644 --- a/src/main/java/org/hyperskill/hstest/testing/ProcessWrapper.java +++ b/src/main/java/org/hyperskill/hstest/testing/ProcessWrapper.java @@ -33,10 +33,10 @@ public class ProcessWrapper { private boolean terminated = false; private final List cpuLoadHistory = new LinkedList<>(); - private final int cpuLoadHistoryMax = 10; + private final int cpuLoadHistoryMax = 2; private final List outputDiffHistory = new LinkedList<>(); - private final int outputDiffHistoryMax = 3; + private final int outputDiffHistoryMax = 2; @Getter @Setter boolean checkEarlyFinish = false; @Getter @Setter boolean registerOutput = true; @@ -71,7 +71,6 @@ public void provideInput(ByteArrayOutputStream input) { try { stdin.write(input.toByteArray()); } catch (IOException e) { - String command = String.join(" ", args); StageTest.getCurrTestRun().setErrorInTest( new UnexpectedError("Can't provide input to the process\n" + command)); } From 2e473e173316350387f6d76c35fe78f2dc051068 Mon Sep 17 00:00:00 2001 From: aaaaaa2493 Date: Mon, 25 Oct 2021 12:24:15 +0300 Subject: [PATCH 07/55] Fix starting process --- .../java/org/hyperskill/hstest/testing/ProcessWrapper.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/hyperskill/hstest/testing/ProcessWrapper.java b/src/main/java/org/hyperskill/hstest/testing/ProcessWrapper.java index 54a2221e..62b24ee6 100644 --- a/src/main/java/org/hyperskill/hstest/testing/ProcessWrapper.java +++ b/src/main/java/org/hyperskill/hstest/testing/ProcessWrapper.java @@ -90,7 +90,11 @@ public ProcessWrapper start() { stdin = process.getOutputStream(); } catch (Exception ex) { - throw new UnexpectedError("Cannot start process\n" + command, ex); + StageTest.getCurrTestRun().setErrorInTest( + new UnexpectedError("Cannot start process\n" + command, ex)); + alive = false; + terminated = true; + return this; } if (group == null) { From b5f42dbec0a82b93c379edc34001866ff2253ecc Mon Sep 17 00:00:00 2001 From: aaaaaa2493 Date: Mon, 25 Oct 2021 19:24:30 +0300 Subject: [PATCH 08/55] Implement searchers and runners for Go, Python --- .../hyperskill/hstest/common/FileUtils.java | 94 +++++-- .../hstest/dynamic/SystemHandler.java | 1 + .../hyperskill/hstest/stage/StageTest.java | 4 +- .../testing/execution/ProcessExecutor.java | 2 +- .../execution/filtering/FileFilter.java | 49 ++++ .../execution/filtering/MainFilter.java | 14 + .../testing/execution/process/GoExecutor.java | 53 +++- .../execution/process/JavascriptExecutor.java | 12 +- .../execution/process/PythonExecutor.java | 12 +- .../runnable/PythonRunnableFile.java | 14 + .../execution/runnable/RunnableFile.java | 48 +++- .../execution/searcher/BaseSearcher.java | 265 ++++++++++++++++++ .../execution/searcher/GoSearcher.java | 32 +++ .../searcher/JavascriptSearcher.java | 32 +++ .../execution/searcher/PythonSearcher.java | 89 ++++++ 15 files changed, 689 insertions(+), 32 deletions(-) create mode 100644 src/main/java/org/hyperskill/hstest/testing/execution/filtering/FileFilter.java create mode 100644 src/main/java/org/hyperskill/hstest/testing/execution/filtering/MainFilter.java create mode 100644 src/main/java/org/hyperskill/hstest/testing/execution/runnable/PythonRunnableFile.java create mode 100644 src/main/java/org/hyperskill/hstest/testing/execution/searcher/BaseSearcher.java create mode 100644 src/main/java/org/hyperskill/hstest/testing/execution/searcher/GoSearcher.java create mode 100644 src/main/java/org/hyperskill/hstest/testing/execution/searcher/JavascriptSearcher.java create mode 100644 src/main/java/org/hyperskill/hstest/testing/execution/searcher/PythonSearcher.java diff --git a/src/main/java/org/hyperskill/hstest/common/FileUtils.java b/src/main/java/org/hyperskill/hstest/common/FileUtils.java index 789a06d6..c2fb975c 100644 --- a/src/main/java/org/hyperskill/hstest/common/FileUtils.java +++ b/src/main/java/org/hyperskill/hstest/common/FileUtils.java @@ -2,18 +2,21 @@ import lombok.Data; import org.hyperskill.hstest.dynamic.SystemHandler; +import org.hyperskill.hstest.exception.outcomes.UnexpectedError; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; +import java.util.stream.Collectors; import static java.util.stream.Collectors.toList; @@ -30,14 +33,14 @@ public static void createFiles(Map files) throws IOException { for (Map.Entry fileEntry : files.entrySet()) { String filename = fileEntry.getKey(); String content = fileEntry.getValue(); - Files.write(Paths.get(CURRENT_DIR + filename), content.getBytes()); + Files.write(Paths.get(abspath(filename)), content.getBytes()); } } public static void deleteFiles(Map files) throws IOException { for (Map.Entry fileEntry : files.entrySet()) { String filename = fileEntry.getKey(); - Files.deleteIfExists(Paths.get(CURRENT_DIR + filename)); + Files.deleteIfExists(Paths.get(abspath(filename))); } } @@ -60,7 +63,7 @@ public static String getNonexistentFilePath(String extension) { while (true) { final String fileName = TEMP_FILE_PREFIX + i + extension; - final Path path = Paths.get(CURRENT_DIR + fileName); + final Path path = Paths.get(abspath(fileName)); if (!RETURNED_NONEXISTENT_FILES.contains(fileName) && Files.notExists(path)) { RETURNED_NONEXISTENT_FILES.add(fileName); @@ -76,10 +79,7 @@ public static String getNonexistentFilePath() { } public static String readFile(String name) { - if (!name.startsWith(CURRENT_DIR)) { - name = CURRENT_DIR + name; - } - Path path = Paths.get(name); + Path path = Paths.get(abspath(name)); try { return Files.readString(path); } catch (IOException ignored) { @@ -94,14 +94,20 @@ public static class Folder { final List files; } - public static Iterable walkUserFiles(String folder) throws IOException { - var currFolder = new File(folder).getAbsolutePath(); - var testFolder = new File(currFolder, "test").getAbsolutePath(); + public static Iterable walkUserFiles(String folder) { + var currFolder = abspath(folder); + var testFolder = join(currFolder, "test"); + + Iterator walk; - Iterator walk = Files.walk(Paths.get(currFolder)) - .filter(Files::isDirectory) - .filter(path -> !path.startsWith(Paths.get(testFolder))) - .iterator(); + try { + walk = Files.walk(Paths.get(currFolder)) + .filter(Files::isDirectory) + .filter(path -> !path.startsWith(Paths.get(testFolder))) + .iterator(); + } catch (IOException ex) { + throw new UnexpectedError("Error while walking in " + folder, ex); + } return () -> new Iterator<>() { @Override @@ -133,8 +139,19 @@ public Folder next() { }; } + public static String displaySorted(Collection files) { + return files.stream() + .map(FileUtils::abspath) + .sorted() + .map(s -> "\"" + s + "\"") + .collect(Collectors.joining(", ")); + } + + // Implementations of Python's "os.path.*" functions + // since Java has no straightforward way to navigate around filesystem. + public static String cwd() { - return new File(System.getProperty(SystemHandler.separatorProperty)).getAbsolutePath(); + return abspath(System.getProperty(SystemHandler.workingDirectoryProperty)); } public static void chdir(String folder) { @@ -142,7 +159,52 @@ public static void chdir(String folder) { } public static void chdir(File folder) { - System.setProperty(SystemHandler.separatorProperty, folder.getAbsolutePath()); + System.setProperty(SystemHandler.workingDirectoryProperty, abspath(folder)); + } + + public static String abspath(String path) { + return abspath(new File(path)); + } + + public static String abspath(File file) { + return file.getAbsolutePath(); + } + + public static String join(String folder, String file) { + return join(new File(folder), file); } + public static String join(File folder, String file) { + return abspath(new File(abspath(folder), file)); + } + + public static boolean exists(String path) { + return new File(abspath(path)).exists(); + } + + public static boolean isdir(String path) { + return new File(abspath(path)).isDirectory(); + } + + public static boolean isfile(String path) { + return new File(abspath(path)).isFile(); + } + + public static void main(String[] args) { + for (var f : walkUserFiles(cwd())) { + System.out.println(f.folder.getAbsolutePath()); + System.out.println(abspath(f.folder)); + System.out.println(f.folder.getName()); + for (var g : f.files) { + System.out.println(" FILE " + g.getName() + "\n " + + g.getAbsolutePath() + "\n " + abspath(g)); + } + for (var g : f.dirs) { + System.out.println(" DIR " + g.getName() + " \n " + + g.getAbsolutePath() + "\n " + abspath(g)); + } + System.out.println(); + System.out.println("---"); + } + } } diff --git a/src/main/java/org/hyperskill/hstest/dynamic/SystemHandler.java b/src/main/java/org/hyperskill/hstest/dynamic/SystemHandler.java index 3d407b94..3d625631 100644 --- a/src/main/java/org/hyperskill/hstest/dynamic/SystemHandler.java +++ b/src/main/java/org/hyperskill/hstest/dynamic/SystemHandler.java @@ -25,6 +25,7 @@ private SystemHandler() { } private static String oldLineSeparator; private static String oldDefaultCharset; + public static final String workingDirectoryProperty = "user.dir"; public static final String separatorProperty = "line.separator"; public static final String defaultCharsetProperty = "file.encoding"; diff --git a/src/main/java/org/hyperskill/hstest/stage/StageTest.java b/src/main/java/org/hyperskill/hstest/stage/StageTest.java index 08ae0689..37b18e21 100644 --- a/src/main/java/org/hyperskill/hstest/stage/StageTest.java +++ b/src/main/java/org/hyperskill/hstest/stage/StageTest.java @@ -72,7 +72,7 @@ public StageTest(Class testedClass) { this(testedClass.getName()); } - private TestRunner initRunner() throws IOException { + private TestRunner initRunner() { for (var folder : walkUserFiles(".")) { for (var file : folder.getFiles()) { if (file.getName().endsWith(".go")) { @@ -90,7 +90,7 @@ private TestRunner initRunner() throws IOException { return new AsyncDynamicTestingRunner(); } - private List initTests() throws IOException { + private List initTests() { if (runner == null) { runner = initRunner(); } diff --git a/src/main/java/org/hyperskill/hstest/testing/execution/ProcessExecutor.java b/src/main/java/org/hyperskill/hstest/testing/execution/ProcessExecutor.java index 66d5d3d3..ed0552f6 100644 --- a/src/main/java/org/hyperskill/hstest/testing/execution/ProcessExecutor.java +++ b/src/main/java/org/hyperskill/hstest/testing/execution/ProcessExecutor.java @@ -28,7 +28,7 @@ public abstract class ProcessExecutor extends ProgramExecutor { private ThreadGroup group; private boolean continueExecuting = true; - private final RunnableFile runnable; + protected final RunnableFile runnable; protected ProcessExecutor(RunnableFile runnable) { this.runnable = runnable; diff --git a/src/main/java/org/hyperskill/hstest/testing/execution/filtering/FileFilter.java b/src/main/java/org/hyperskill/hstest/testing/execution/filtering/FileFilter.java new file mode 100644 index 00000000..ef9fce82 --- /dev/null +++ b/src/main/java/org/hyperskill/hstest/testing/execution/filtering/FileFilter.java @@ -0,0 +1,49 @@ +package org.hyperskill.hstest.testing.execution.filtering; + +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; + +import java.io.File; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; + +@Accessors(fluent = true, chain = true) +public class FileFilter { + + public interface InitFilter { + void apply(String folder, Map sources); + } + + public interface Filter { + boolean apply(String thing); + } + + public interface GenericFilter { + boolean apply(String folder, String file, String source); + } + + @Getter @Setter private InitFilter initFiles = (f, s) -> { }; + @Getter @Setter private Filter folder = f -> true; + @Getter @Setter private Filter file = f -> true; + @Getter @Setter private Filter source = f -> true; + @Getter @Setter private GenericFilter generic = (f1, f2, s) -> true; + @Getter @Setter private Set filtered = null; + + public static Filter regexFilter(Pattern regex) { + return t -> regex.matcher(t).find(); + } + + public final void initFilter(String folder, Map sources) { + initFiles.apply(folder, sources); + } + + public final boolean filter(String folder, String file, String source) { + return this.folder.apply(folder) + && this.file.apply(file) + && this.source.apply(source) + && this.generic.apply(folder, file, source); + } + +} diff --git a/src/main/java/org/hyperskill/hstest/testing/execution/filtering/MainFilter.java b/src/main/java/org/hyperskill/hstest/testing/execution/filtering/MainFilter.java new file mode 100644 index 00000000..ed02a251 --- /dev/null +++ b/src/main/java/org/hyperskill/hstest/testing/execution/filtering/MainFilter.java @@ -0,0 +1,14 @@ +package org.hyperskill.hstest.testing.execution.filtering; + +import lombok.Getter; +import lombok.Setter; + +public class MainFilter extends FileFilter { + + @Getter @Setter String programShouldContain; + + public MainFilter(String programShouldContain) { + this.programShouldContain = programShouldContain; + } + +} diff --git a/src/main/java/org/hyperskill/hstest/testing/execution/process/GoExecutor.java b/src/main/java/org/hyperskill/hstest/testing/execution/process/GoExecutor.java index a863970f..3f87bcf9 100644 --- a/src/main/java/org/hyperskill/hstest/testing/execution/process/GoExecutor.java +++ b/src/main/java/org/hyperskill/hstest/testing/execution/process/GoExecutor.java @@ -1,17 +1,62 @@ package org.hyperskill.hstest.testing.execution.process; import org.hyperskill.hstest.testing.execution.ProcessExecutor; -import org.hyperskill.hstest.testing.execution.runnable.RunnableFile; +import org.hyperskill.hstest.testing.execution.searcher.GoSearcher; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; + +import static org.hyperskill.hstest.common.FileUtils.abspath; +import static org.hyperskill.hstest.common.OsUtils.isWindows; public class GoExecutor extends ProcessExecutor { - protected GoExecutor(RunnableFile source) { - super(source); + private final String executable; + private final String filename; + + protected GoExecutor(String sourceName) { + super(new GoSearcher().find(sourceName)); + + var fileName = runnable.getFile().getName(); + + var withoutGo = fileName + .substring(0, fileName.length() - new GoSearcher().extension().length()); + + if (isWindows()) { + executable = withoutGo; + filename = executable + ".exe"; + } else { + executable = "./" + withoutGo; + filename = withoutGo; + } + } + + @Override + protected List compilationCommand() { + return List.of("go", "build", runnable.getFile().getAbsolutePath()); + } + + @Override + protected String filterCompilationError(String error) { + return error.lines().filter(line -> !line.startsWith("#")).collect(Collectors.joining("\n")); } @Override protected List executionCommand(List args) { - return null; + List fullArgs = new ArrayList<>(); + fullArgs.add(executable); + fullArgs.addAll(args); + + return fullArgs; + } + + @Override + protected void cleanup() { + try { + Files.deleteIfExists(Paths.get(abspath(filename))); + } catch (IOException ignored) { } } } diff --git a/src/main/java/org/hyperskill/hstest/testing/execution/process/JavascriptExecutor.java b/src/main/java/org/hyperskill/hstest/testing/execution/process/JavascriptExecutor.java index 2451b096..c1b2fe98 100644 --- a/src/main/java/org/hyperskill/hstest/testing/execution/process/JavascriptExecutor.java +++ b/src/main/java/org/hyperskill/hstest/testing/execution/process/JavascriptExecutor.java @@ -2,16 +2,22 @@ import org.hyperskill.hstest.testing.execution.ProcessExecutor; import org.hyperskill.hstest.testing.execution.runnable.RunnableFile; +import org.hyperskill.hstest.testing.execution.searcher.JavascriptSearcher; +import java.util.ArrayList; import java.util.List; public class JavascriptExecutor extends ProcessExecutor { - protected JavascriptExecutor(RunnableFile runnable) { - super(runnable); + protected JavascriptExecutor(String sourceName) { + super(new JavascriptSearcher().find(sourceName)); } @Override protected List executionCommand(List args) { - return null; + List fullArgs = new ArrayList<>(); + fullArgs.addAll(List.of("node", runnable.getFile().getAbsolutePath())); + fullArgs.addAll(args); + + return fullArgs; } } diff --git a/src/main/java/org/hyperskill/hstest/testing/execution/process/PythonExecutor.java b/src/main/java/org/hyperskill/hstest/testing/execution/process/PythonExecutor.java index ca8097a9..6e452618 100644 --- a/src/main/java/org/hyperskill/hstest/testing/execution/process/PythonExecutor.java +++ b/src/main/java/org/hyperskill/hstest/testing/execution/process/PythonExecutor.java @@ -2,16 +2,22 @@ import org.hyperskill.hstest.testing.execution.ProcessExecutor; import org.hyperskill.hstest.testing.execution.runnable.RunnableFile; +import org.hyperskill.hstest.testing.execution.searcher.PythonSearcher; +import java.util.ArrayList; import java.util.List; public class PythonExecutor extends ProcessExecutor { - protected PythonExecutor(RunnableFile runnable) { - super(runnable); + protected PythonExecutor(String sourceName) { + super(new PythonSearcher().find(sourceName)); } @Override protected List executionCommand(List args) { - return null; + List fullArgs = new ArrayList<>(); + fullArgs.addAll(List.of("python", "-u", runnable.getFile().getAbsolutePath())); + fullArgs.addAll(args); + + return fullArgs; } } diff --git a/src/main/java/org/hyperskill/hstest/testing/execution/runnable/PythonRunnableFile.java b/src/main/java/org/hyperskill/hstest/testing/execution/runnable/PythonRunnableFile.java new file mode 100644 index 00000000..a4e3cb67 --- /dev/null +++ b/src/main/java/org/hyperskill/hstest/testing/execution/runnable/PythonRunnableFile.java @@ -0,0 +1,14 @@ +package org.hyperskill.hstest.testing.execution.runnable; + +import lombok.Getter; + +import java.io.File; + +public class PythonRunnableFile extends RunnableFile { + @Getter private final String module; + + public PythonRunnableFile(File folder, File file, String module) { + super(folder, file); + this.module = module; + } +} diff --git a/src/main/java/org/hyperskill/hstest/testing/execution/runnable/RunnableFile.java b/src/main/java/org/hyperskill/hstest/testing/execution/runnable/RunnableFile.java index b0179bd5..a10867d4 100644 --- a/src/main/java/org/hyperskill/hstest/testing/execution/runnable/RunnableFile.java +++ b/src/main/java/org/hyperskill/hstest/testing/execution/runnable/RunnableFile.java @@ -1,11 +1,53 @@ package org.hyperskill.hstest.testing.execution.runnable; -import lombok.Data; - import java.io.File; -@Data public class RunnableFile { final File folder; final File file; + + public RunnableFile(File folder, File file) { + this.folder = folder; + this.file = file; + } + + public File getFolder() { + return this.folder; + } + + public File getFile() { + return this.file; + } + + public boolean equals(final Object o) { + if (o == this) return true; + if (!(o instanceof RunnableFile)) return false; + final RunnableFile other = (RunnableFile) o; + if (!other.canEqual((Object) this)) return false; + final Object this$folder = this.getFolder(); + final Object other$folder = other.getFolder(); + if (this$folder == null ? other$folder != null : !this$folder.equals(other$folder)) return false; + final Object this$file = this.getFile(); + final Object other$file = other.getFile(); + if (this$file == null ? other$file != null : !this$file.equals(other$file)) return false; + return true; + } + + protected boolean canEqual(final Object other) { + return other instanceof RunnableFile; + } + + public int hashCode() { + final int PRIME = 59; + int result = 1; + final Object $folder = this.getFolder(); + result = result * PRIME + ($folder == null ? 43 : $folder.hashCode()); + final Object $file = this.getFile(); + result = result * PRIME + ($file == null ? 43 : $file.hashCode()); + return result; + } + + public String toString() { + return "RunnableFile(folder=" + this.getFolder() + ", file=" + this.getFile() + ")"; + } } diff --git a/src/main/java/org/hyperskill/hstest/testing/execution/searcher/BaseSearcher.java b/src/main/java/org/hyperskill/hstest/testing/execution/searcher/BaseSearcher.java new file mode 100644 index 00000000..da134be0 --- /dev/null +++ b/src/main/java/org/hyperskill/hstest/testing/execution/searcher/BaseSearcher.java @@ -0,0 +1,265 @@ +package org.hyperskill.hstest.testing.execution.searcher; + +import lombok.Data; +import org.hyperskill.hstest.common.FileUtils; +import org.hyperskill.hstest.exception.outcomes.ErrorWithFeedback; +import org.hyperskill.hstest.exception.outcomes.UnexpectedError; +import org.hyperskill.hstest.testing.execution.filtering.FileFilter; +import org.hyperskill.hstest.testing.execution.filtering.MainFilter; +import org.hyperskill.hstest.testing.execution.runnable.RunnableFile; + +import java.io.File; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static java.util.stream.Collectors.toSet; + +public abstract class BaseSearcher { + + @Data + private static class CacheKey { + final String extension; + final String whereToSearch; + } + + private static final Map fileContentsCached = new HashMap<>(); + private static final Map searchCached = new HashMap<>(); + private static final String moduleSeparator = "."; + + + public abstract String extension(); + public abstract RunnableFile search(String whereToSearch); + + private static Map getContents(List files) { + var contents = new HashMap(); + + for (File file : files) { + var path = FileUtils.abspath(file); + + if (fileContentsCached.containsKey(path)) { + contents.put(file.getName(), fileContentsCached.get(path)); + + } else if (FileUtils.exists(path)) { + String fileContent = FileUtils.readFile(path); + + if (fileContent != null) { + contents.put(file.getName(), fileContent); + fileContentsCached.put(path, fileContent); + } + } + } + + return contents; + } + + private RunnableFile searchNonCached( + String whereToSearch, + FileFilter fileFilter, + FileFilter preMainFilter, + MainFilter mainFilter, + FileFilter postMainFilter + ) { + + var currFolder = FileUtils.abspath(whereToSearch); + + for (var walk : FileUtils.walkUserFiles(currFolder)) { + var folder = walk.getFolder(); + var dirs = walk.getDirs(); + var files = walk.getFiles(); + + var contents = getContents(files); + + var initialFilter = new FileFilter() + .file(f -> f.endsWith(extension())) + .generic(fileFilter::filter); + + Set candidates = new HashSet<>(files); + + for (var currFilter : new FileFilter[] { + initialFilter, preMainFilter, mainFilter, postMainFilter }) { + + currFilter.initFilter(FileUtils.abspath(folder), contents); + + var filteredFiles = files.stream() + .filter(file -> currFilter.filter( + FileUtils.abspath(folder), + FileUtils.abspath(file), + contents.get(file.getName()) + )).collect(toSet()); + + currFilter.filtered(filteredFiles); + + if (filteredFiles.size() == 0) { + if (currFilter == initialFilter) { + break; + } else { + continue; + } + } + + if (filteredFiles.size() == 1) { + var file = filteredFiles.iterator().next(); + return new RunnableFile(folder, file); + } + + var newCandidates = new HashSet<>(candidates); + newCandidates.retainAll(filteredFiles); + + if (newCandidates.size() != 0) { + candidates = newCandidates; + } + + if (candidates.size() == 1) { + var file = candidates.iterator().next(); + return new RunnableFile(folder, file); + } + } + + if (initialFilter.filtered().size() == 0) { + continue; + } + + if (candidates.size() > 1 && mainFilter.filtered().size() > 0) { + var strFiles = FileUtils.displaySorted(candidates); + throw new ErrorWithFeedback( + "Cannot decide which file to run out of the following: " + strFiles + "\n" + + "They all have " + mainFilter.getProgramShouldContain() + ". Leave one file with this line." + ); + } + + if (candidates.size() == 0) { + candidates = initialFilter.filtered(); + } + + var strFiles = FileUtils.displaySorted(candidates); + + throw new ErrorWithFeedback( + "Cannot decide which file to run out of the following: " + strFiles + "\n" + + "Write \"" + mainFilter.getProgramShouldContain() + " in one of them to mark it as an entry point." + ); + } + + throw new ErrorWithFeedback( + "Cannot find a file to execute your code.\n" + + "Are your project files located at \"" + currFolder + "\"?" + ); + } + + protected RunnableFile searchCached( + String whereToSearch, + FileFilter fileFilter, + FileFilter preMainFilter, + MainFilter mainFilter, + FileFilter postMainFilter + ) { + + if (!extension().startsWith(".")) { + throw new UnexpectedError( + "File extension \"" + extension() + "\" should start with a dot"); + } + + if (whereToSearch == null) { + whereToSearch = FileUtils.cwd(); + } + + boolean doCaching = false; + var cacheKey = new CacheKey(extension(), whereToSearch); + + if (fileFilter == null) { + if (searchCached.containsKey(cacheKey)) { + return searchCached.get(cacheKey); + } + + doCaching = true; + fileFilter = new FileFilter(); + } + + if (preMainFilter == null) { + preMainFilter = new FileFilter(); + } + + if (mainFilter == null) { + mainFilter = new MainFilter(""); + } + + if (postMainFilter == null) { + postMainFilter = new FileFilter(); + } + + var result = searchNonCached( + whereToSearch, fileFilter, preMainFilter, mainFilter, postMainFilter); + + if (doCaching) { + searchCached.put(cacheKey, result); + } + + return result; + } + + @Data + private static class ParsedSource { + final String folder; + final String file; + final String module; + } + + public RunnableFile find(String source) { + if (source == null) { + return search(null); + } + + var ext = extension(); + + var parsed = parseSource(source); + var sourceFolder = parsed.folder; + var sourceFile = parsed.file; + var sourceModule = parsed.module; + + if (sourceFolder != null && FileUtils.isdir(sourceFolder)) { + return searchCached(sourceFolder, + null, null, null, null); + + } else if (sourceFile != null && FileUtils.isfile(sourceFile)) { + var index = sourceModule.lastIndexOf(moduleSeparator); + var path = sourceModule.substring(0, index); + var file = sourceModule.substring(index + 1); + var folder = FileUtils.abspath(path.replace(moduleSeparator, File.separator)); + return new RunnableFile(new File(folder), new File(file + ext)); + + } else { + return searchCached(null, + null, null, null, null); + } + } + + private ParsedSource parseSource(String source) { + var ext = extension(); + + source = source + .replace("/", File.separator) + .replace("\\", File.separator); + + if (source.endsWith(ext)) { + var module = source + .substring(0, source.length() - ext.length()) + .replace(File.separator, moduleSeparator); + + return new ParsedSource(null, source, module); + + } else if (source.contains(File.separator)) { + if (source.endsWith(File.separator)) { + source = source.substring(0, source.length() - File.separator.length()); + } + var module = source.replace(File.separator, moduleSeparator); + return new ParsedSource(source, null, module); + + } else { + var sourceFolder = source.replace(moduleSeparator, File.separator); + var sourceFile = sourceFolder + ext; + return new ParsedSource(sourceFolder, sourceFile, source); + } + } +} diff --git a/src/main/java/org/hyperskill/hstest/testing/execution/searcher/GoSearcher.java b/src/main/java/org/hyperskill/hstest/testing/execution/searcher/GoSearcher.java new file mode 100644 index 00000000..a055f73b --- /dev/null +++ b/src/main/java/org/hyperskill/hstest/testing/execution/searcher/GoSearcher.java @@ -0,0 +1,32 @@ +package org.hyperskill.hstest.testing.execution.searcher; + +import org.hyperskill.hstest.testing.execution.filtering.FileFilter; +import org.hyperskill.hstest.testing.execution.filtering.MainFilter; +import org.hyperskill.hstest.testing.execution.runnable.RunnableFile; + +import java.util.regex.Pattern; + +import static java.util.regex.Pattern.MULTILINE; + +public class GoSearcher extends BaseSearcher { + @Override + public String extension() { + return ".go"; + } + + @Override + public RunnableFile search(String whereToSearch) { + var mainSearcher = Pattern.compile("(^|\n) *func +main +\\( *\\)", MULTILINE); + + var mainFilter = new MainFilter("func main()"); + mainFilter.source(FileFilter.regexFilter(mainSearcher)); + + return searchCached( + whereToSearch, + null, + null, + mainFilter, + null + ); + } +} diff --git a/src/main/java/org/hyperskill/hstest/testing/execution/searcher/JavascriptSearcher.java b/src/main/java/org/hyperskill/hstest/testing/execution/searcher/JavascriptSearcher.java new file mode 100644 index 00000000..1c69a0b4 --- /dev/null +++ b/src/main/java/org/hyperskill/hstest/testing/execution/searcher/JavascriptSearcher.java @@ -0,0 +1,32 @@ +package org.hyperskill.hstest.testing.execution.searcher; + +import org.hyperskill.hstest.testing.execution.filtering.FileFilter; +import org.hyperskill.hstest.testing.execution.filtering.MainFilter; +import org.hyperskill.hstest.testing.execution.runnable.RunnableFile; + +import java.util.regex.Pattern; + +import static java.util.regex.Pattern.MULTILINE; + +public class JavascriptSearcher extends BaseSearcher { + @Override + public String extension() { + return ".js"; + } + + @Override + public RunnableFile search(String whereToSearch) { + var mainSearcher = Pattern.compile("(^|\n) *function +main +\\( *\\)", MULTILINE); + + var mainFilter = new MainFilter("function main()"); + mainFilter.source(FileFilter.regexFilter(mainSearcher)); + + return searchCached( + whereToSearch, + null, + null, + mainFilter, + null + ); + } +} diff --git a/src/main/java/org/hyperskill/hstest/testing/execution/searcher/PythonSearcher.java b/src/main/java/org/hyperskill/hstest/testing/execution/searcher/PythonSearcher.java new file mode 100644 index 00000000..bc5bf070 --- /dev/null +++ b/src/main/java/org/hyperskill/hstest/testing/execution/searcher/PythonSearcher.java @@ -0,0 +1,89 @@ +package org.hyperskill.hstest.testing.execution.searcher; + +import org.hyperskill.hstest.testing.execution.filtering.FileFilter; +import org.hyperskill.hstest.testing.execution.filtering.MainFilter; +import org.hyperskill.hstest.testing.execution.runnable.PythonRunnableFile; +import org.hyperskill.hstest.testing.execution.runnable.RunnableFile; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +import static java.util.regex.Pattern.MULTILINE; +import static java.util.regex.Pattern.compile; + +public class PythonSearcher extends BaseSearcher { + Map isImported = null; + + @Override + public String extension() { + return ".py"; + } + + @Override + public RunnableFile search(String whereToSearch) { + return search(whereToSearch, null); + } + + public RunnableFile search(String whereToSearch, FileFilter fileFilter) { + isImported = new HashMap<>(); + + var initFilter = new FileFilter() + .initFiles(this::initRegexes) + .file(f -> !isImported.get(f)); + + var mainFilter = new MainFilter("if __name__ == '__main__'"); + mainFilter.source(s -> s.contains("__name__") && s.contains("__main__")); + + return searchCached( + whereToSearch, + fileFilter, + initFilter, + mainFilter, + null + ); + } + + private void initRegexes(String folder, Map sources) { + Map> importRegexes = new HashMap<>(); + + for (var item : sources.entrySet()) { + var file = item.getKey(); + var withoutExt = file.substring(0, file.length() - extension().length()); + + isImported.put(file, false); + + importRegexes.put(file, List.of( + compile("(^|\n)import +[\\w., ]*\\b" + withoutExt + "\\b[\\w., ]*", MULTILINE), + compile("(^|\n)from +\\.? *\\b" + withoutExt + "\\b +import +", MULTILINE) + )); + } + + for (var item : sources.entrySet()) { + var source = item.getValue(); + + for (var item2 : importRegexes.entrySet()) { + var f = item2.getKey(); + var r1 = item2.getValue().get(0); + var r2 = item2.getValue().get(1); + + if (r1.matcher(source).find() || r2.matcher(source).find()) { + isImported.put(f, true); + } + } + } + } + + @Override + public PythonRunnableFile find(String whereToSearch) { + var runnable = super.find(whereToSearch); + var name = runnable.getFile().getName(); + + return new PythonRunnableFile( + runnable.getFolder(), + runnable.getFile(), + name.substring(0, name.length() - extension().length()) + ); + } +} From fcc565c1fa17f2d52c03eaa081b13885f2dcfe2c Mon Sep 17 00:00:00 2001 From: aaaaaa2493 Date: Wed, 27 Oct 2021 21:27:01 +0300 Subject: [PATCH 09/55] Remove test code --- .../hyperskill/hstest/common/FileUtils.java | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/main/java/org/hyperskill/hstest/common/FileUtils.java b/src/main/java/org/hyperskill/hstest/common/FileUtils.java index c2fb975c..b9db08e8 100644 --- a/src/main/java/org/hyperskill/hstest/common/FileUtils.java +++ b/src/main/java/org/hyperskill/hstest/common/FileUtils.java @@ -189,22 +189,4 @@ public static boolean isdir(String path) { public static boolean isfile(String path) { return new File(abspath(path)).isFile(); } - - public static void main(String[] args) { - for (var f : walkUserFiles(cwd())) { - System.out.println(f.folder.getAbsolutePath()); - System.out.println(abspath(f.folder)); - System.out.println(f.folder.getName()); - for (var g : f.files) { - System.out.println(" FILE " + g.getName() + "\n " + - g.getAbsolutePath() + "\n " + abspath(g)); - } - for (var g : f.dirs) { - System.out.println(" DIR " + g.getName() + " \n " + - g.getAbsolutePath() + "\n " + abspath(g)); - } - System.out.println(); - System.out.println("---"); - } - } } From 55733d04fda5c28b6b6de58085b6a1926f032170 Mon Sep 17 00:00:00 2001 From: aaaaaa2493 Date: Wed, 27 Oct 2021 21:33:18 +0300 Subject: [PATCH 10/55] Implement dynamic ProgramExecutor creation --- .../hyperskill/hstest/testing/TestRun.java | 2 +- .../hstest/testing/TestedProgram.java | 54 +++++++++++++++---- .../testing/execution/MainMethodExecutor.java | 6 ++- .../runner/AsyncDynamicTestingRunner.java | 2 +- 4 files changed, 50 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/hyperskill/hstest/testing/TestRun.java b/src/main/java/org/hyperskill/hstest/testing/TestRun.java index d32842fd..86490eac 100644 --- a/src/main/java/org/hyperskill/hstest/testing/TestRun.java +++ b/src/main/java/org/hyperskill/hstest/testing/TestRun.java @@ -23,7 +23,7 @@ public class TestRun { - private final TestRunner testRunner; + @Getter private final TestRunner testRunner; @Getter private final int testNum; @Getter private final int testCount; @Getter private final TestCase testCase; diff --git a/src/main/java/org/hyperskill/hstest/testing/TestedProgram.java b/src/main/java/org/hyperskill/hstest/testing/TestedProgram.java index 8c15d685..39958937 100644 --- a/src/main/java/org/hyperskill/hstest/testing/TestedProgram.java +++ b/src/main/java/org/hyperskill/hstest/testing/TestedProgram.java @@ -1,10 +1,14 @@ package org.hyperskill.hstest.testing; import lombok.Getter; +import org.hyperskill.hstest.exception.outcomes.OutcomeError; +import org.hyperskill.hstest.exception.outcomes.UnexpectedError; import org.hyperskill.hstest.stage.StageTest; import org.hyperskill.hstest.testing.execution.MainMethodExecutor; import org.hyperskill.hstest.testing.execution.ProgramExecutor; +import org.hyperskill.hstest.testing.runner.AsyncDynamicTestingRunner; +import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -36,13 +40,11 @@ public class TestedProgram { private List runArgs; /** - * Creates TestedProgram instance, but doesn't run the class - * It is deprecated, use other constructors instead - * @param testedClass class, whose main method you want to test + * Creates TestedProgram instance that will search for a class to run + * and will be able to run it */ - @Deprecated - public TestedProgram(Class testedClass) { - programExecutor = new MainMethodExecutor(testedClass.getName()); + public TestedProgram() { + this((String) null); } /** @@ -51,15 +53,45 @@ public TestedProgram(Class testedClass) { * a particular package if sourceName is a package name */ public TestedProgram(String sourceName) { - programExecutor = new MainMethodExecutor(sourceName); + var runner = StageTest.getCurrTestRun().getTestRunner(); + + if (!(runner instanceof AsyncDynamicTestingRunner)) { + throw new UnexpectedError( + "TestedProgram is supported only while using " + + "AsyncDynamicTestingRunner runner, not " + runner.getClass()); + } + + if (sourceName == null) { + sourceName = StageTest.getCurrTestRun().getTestCase().getSourceName(); + } + + var asyncRunner = (AsyncDynamicTestingRunner) runner; + + try { + programExecutor = asyncRunner + .getExecutor() + .getConstructor(String.class) + .newInstance(sourceName); + } catch (NoSuchMethodException ex) { + throw new UnexpectedError( + "Cannot find proper constructor of class " + asyncRunner.getExecutor(), ex); + } catch (InvocationTargetException | InstantiationException | IllegalAccessException ex) { + if (ex.getCause() instanceof OutcomeError) { + throw (OutcomeError) ex.getCause(); + } + throw new UnexpectedError( + "Cannot instantiate constructor of " + asyncRunner.getExecutor(), ex); + } } /** - * Creates TestedProgram instance that will search for a class to run - * and will be able to run it + * Creates TestedProgram instance, but doesn't run the class + * It is deprecated, use other constructors instead + * @param testedClass class, whose main method you want to test */ - public TestedProgram() { - programExecutor = new MainMethodExecutor(); + @Deprecated + public TestedProgram(Class testedClass) { + programExecutor = new MainMethodExecutor(testedClass.getName()); } private void initProgram(String... args) { diff --git a/src/main/java/org/hyperskill/hstest/testing/execution/MainMethodExecutor.java b/src/main/java/org/hyperskill/hstest/testing/execution/MainMethodExecutor.java index 52e0ac64..ef32d2ce 100644 --- a/src/main/java/org/hyperskill/hstest/testing/execution/MainMethodExecutor.java +++ b/src/main/java/org/hyperskill/hstest/testing/execution/MainMethodExecutor.java @@ -49,7 +49,11 @@ public MainMethodExecutor() { } public MainMethodExecutor(String sourceName) { - initByName(sourceName); + if (sourceName != null) { + initByName(sourceName); + } else { + initByNothing(); + } } private void initByClassInstance(Class clazz) { diff --git a/src/main/java/org/hyperskill/hstest/testing/runner/AsyncDynamicTestingRunner.java b/src/main/java/org/hyperskill/hstest/testing/runner/AsyncDynamicTestingRunner.java index 5340b525..1643deac 100644 --- a/src/main/java/org/hyperskill/hstest/testing/runner/AsyncDynamicTestingRunner.java +++ b/src/main/java/org/hyperskill/hstest/testing/runner/AsyncDynamicTestingRunner.java @@ -33,7 +33,7 @@ public AsyncDynamicTestingRunner(Class executor) { this.executor = executor; } - Class getExecutor() { + public Class getExecutor() { return executor; } From 7079a0bb5e179af109aa66b5c5f73212a7506e04 Mon Sep 17 00:00:00 2001 From: aaaaaa2493 Date: Wed, 27 Oct 2021 23:20:57 +0300 Subject: [PATCH 11/55] Use initial delay ensuring user program has started --- .../hstest/testing/ProcessWrapper.java | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/hyperskill/hstest/testing/ProcessWrapper.java b/src/main/java/org/hyperskill/hstest/testing/ProcessWrapper.java index 62b24ee6..0e1021f1 100644 --- a/src/main/java/org/hyperskill/hstest/testing/ProcessWrapper.java +++ b/src/main/java/org/hyperskill/hstest/testing/ProcessWrapper.java @@ -38,6 +38,9 @@ public class ProcessWrapper { private final List outputDiffHistory = new LinkedList<>(); private final int outputDiffHistoryMax = 2; + private boolean initialIdleWait = true; + private final int initialIdleWaitMax = 10; + @Getter @Setter boolean checkEarlyFinish = false; @Getter @Setter boolean registerOutput = true; @@ -175,7 +178,7 @@ private void checkPipe(InputStream readPipe, } } - private void checkStdout() { + private void checkStdout() { checkPipe(process.getInputStream(), System.out, stdout); } @@ -199,7 +202,16 @@ private void checkCpuLoad() { oldCpuTime = currCpuTime; cpuLoadHistory.add(currCpuLoad); - if (cpuLoadHistory.size() > cpuLoadHistoryMax) { + + if (initialIdleWait && currCpuTime != 0) { + initialIdleWait = false; + } + + if (initialIdleWait && cpuLoadHistory.size() > initialIdleWaitMax) { + initialIdleWait = false; + } + + if (!initialIdleWait && cpuLoadHistory.size() > cpuLoadHistoryMax) { cpuLoadHistory.remove(0); } @@ -221,12 +233,20 @@ private void checkOutput() { outputDiffHistory.remove(0); } + if (initialIdleWait && diff > 0) { + initialIdleWait = false; + } + sleep(10); checkAlive(); } } public boolean isWaitingInput() { + if (initialIdleWait) { + return false; + } + boolean programNotLoadingProcessor = cpuLoadHistory.size() >= cpuLoadHistoryMax && cpuLoadHistory.stream().mapToLong(e -> e).sum() < 1; @@ -243,6 +263,7 @@ public void registerInputRequest() { throw new UnexpectedError("Program is not waiting for the input\n" + command); } cpuLoadHistory.clear(); + outputDiffHistory.clear(); } public boolean isFinished() { From 51469897b69d489dd7d8e9b0208f06bdebc3b8db Mon Sep 17 00:00:00 2001 From: aaaaaa2493 Date: Thu, 28 Oct 2021 03:20:29 +0300 Subject: [PATCH 12/55] Finish implementing checking Python, Go, JavaScript solutions --- .gitignore | 2 ++ .../hyperskill/hstest/common/FileUtils.java | 9 +++++- .../hstest/common/ReflectionUtils.java | 30 +++++++++++++++++ .../hstest/dynamic/SystemHandler.java | 4 +++ .../hstest/dynamic/output/OutputMock.java | 2 ++ .../hstest/outcomes/ExceptionOutcome.java | 4 ++- .../hyperskill/hstest/outcomes/Outcome.java | 9 ++++-- .../hyperskill/hstest/stage/StageTest.java | 14 ++++++-- .../hstest/testing/ProcessWrapper.java | 32 ++++++++++--------- .../testing/execution/process/GoExecutor.java | 4 +-- .../execution/process/JavaExecutor.java | 2 +- .../execution/process/JavascriptExecutor.java | 2 +- .../execution/process/PythonExecutor.java | 2 +- .../execution/searcher/BaseSearcher.java | 2 +- .../runner/AsyncDynamicTestingRunner.java | 8 +++++ 15 files changed, 97 insertions(+), 29 deletions(-) diff --git a/.gitignore b/.gitignore index 5a798f3b..3ba16c1a 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,5 @@ gradle/* .gradle/* gradlew gradlew.bat + +*.exe diff --git a/src/main/java/org/hyperskill/hstest/common/FileUtils.java b/src/main/java/org/hyperskill/hstest/common/FileUtils.java index b9db08e8..3ff1ad32 100644 --- a/src/main/java/org/hyperskill/hstest/common/FileUtils.java +++ b/src/main/java/org/hyperskill/hstest/common/FileUtils.java @@ -155,6 +155,9 @@ public static String cwd() { } public static void chdir(String folder) { + if (folder.equals("C:\\Users\\Vladimir\\Desktop\\hs-test\\src\\test\\java\\outcomes\\src\\test\\java\\outcomes")) { + System.out.println("!!! " + folder); + } chdir(new File(folder)); } @@ -163,7 +166,11 @@ public static void chdir(File folder) { } public static String abspath(String path) { - return abspath(new File(path)); + String cwd = System.getProperty(SystemHandler.workingDirectoryProperty); + if (path.startsWith(cwd)) { + return path; + } + return abspath(new File(cwd, path)); } public static String abspath(File file) { diff --git a/src/main/java/org/hyperskill/hstest/common/ReflectionUtils.java b/src/main/java/org/hyperskill/hstest/common/ReflectionUtils.java index 8c209743..960a28fb 100644 --- a/src/main/java/org/hyperskill/hstest/common/ReflectionUtils.java +++ b/src/main/java/org/hyperskill/hstest/common/ReflectionUtils.java @@ -6,13 +6,16 @@ import org.hyperskill.hstest.exception.outcomes.ErrorWithFeedback; import org.hyperskill.hstest.exception.outcomes.OutcomeError; import org.hyperskill.hstest.exception.outcomes.UnexpectedError; +import org.hyperskill.hstest.stage.StageTest; +import java.io.File; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.net.URISyntaxException; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @@ -222,4 +225,31 @@ public static List getAllFields(Object obj) { .distinct() .collect(Collectors.toList()); } + + public static > boolean isTests(T stage) throws URISyntaxException { + return new File(stage + .getClass() + .getProtectionDomain() + .getCodeSource() + .getLocation().toURI()) + .getAbsolutePath() + .contains(File.separator + "hs-test" + + File.separator + "build" + + File.separator + "classes"); + } + + public static > void setupCwd(T stage) { + String testDir = FileUtils.cwd() + + File.separator + "src" + + File.separator + "test" + + File.separator + "java" + + File.separator + stage.getClass().getPackageName().replace(".", File.separator); + + File file = new File(testDir); + if (file.getName().equals("test")) { + testDir = file.getParent(); + } + + FileUtils.chdir(testDir); + } } diff --git a/src/main/java/org/hyperskill/hstest/dynamic/SystemHandler.java b/src/main/java/org/hyperskill/hstest/dynamic/SystemHandler.java index 3d625631..36e46842 100644 --- a/src/main/java/org/hyperskill/hstest/dynamic/SystemHandler.java +++ b/src/main/java/org/hyperskill/hstest/dynamic/SystemHandler.java @@ -24,6 +24,7 @@ private SystemHandler() { } private static Locale oldLocale; private static String oldLineSeparator; private static String oldDefaultCharset; + private static String oldWorkingDirectory; public static final String workingDirectoryProperty = "user.dir"; public static final String separatorProperty = "line.separator"; @@ -51,6 +52,8 @@ public static void setUp() { oldDefaultCharset = System.getProperty(defaultCharsetProperty); System.setProperty(defaultCharsetProperty, "UTF-8"); + + oldWorkingDirectory = System.getProperty(workingDirectoryProperty); } public static void tearDownSystem() { @@ -68,6 +71,7 @@ public static void tearDownSystem() { Locale.setDefault(oldLocale); System.setProperty(separatorProperty, oldLineSeparator); System.setProperty(defaultCharsetProperty, oldDefaultCharset); + System.setProperty(workingDirectoryProperty, oldWorkingDirectory); } private static void lockSystemForTesting() { diff --git a/src/main/java/org/hyperskill/hstest/dynamic/output/OutputMock.java b/src/main/java/org/hyperskill/hstest/dynamic/output/OutputMock.java index 67c1df9f..476d5393 100644 --- a/src/main/java/org/hyperskill/hstest/dynamic/output/OutputMock.java +++ b/src/main/java/org/hyperskill/hstest/dynamic/output/OutputMock.java @@ -8,10 +8,12 @@ import java.io.ByteArrayOutputStream; import java.io.OutputStream; import java.io.PrintStream; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; import java.util.function.Supplier; +import static org.hyperskill.hstest.dynamic.output.ColoredOutput.BLACK_UNDERLINED; import static org.hyperskill.hstest.dynamic.output.ColoredOutput.BLUE; import static org.hyperskill.hstest.dynamic.output.ColoredOutput.RESET; import static org.hyperskill.hstest.testing.ExecutionOptions.ignoreStdout; diff --git a/src/main/java/org/hyperskill/hstest/outcomes/ExceptionOutcome.java b/src/main/java/org/hyperskill/hstest/outcomes/ExceptionOutcome.java index 1335b055..5db93d16 100644 --- a/src/main/java/org/hyperskill/hstest/outcomes/ExceptionOutcome.java +++ b/src/main/java/org/hyperskill/hstest/outcomes/ExceptionOutcome.java @@ -15,7 +15,9 @@ public ExceptionOutcome(int testNum, ExceptionWithFeedback ex) { String feedback = ex.getErrorText(); testNumber = testNum; - stackTrace = filterStackTrace(getStackTrace(cause)); + if (cause != null) { + stackTrace = filterStackTrace(getStackTrace(cause)); + } errorText = feedback; diff --git a/src/main/java/org/hyperskill/hstest/outcomes/Outcome.java b/src/main/java/org/hyperskill/hstest/outcomes/Outcome.java index 63058bae..98beb6d2 100644 --- a/src/main/java/org/hyperskill/hstest/outcomes/Outcome.java +++ b/src/main/java/org/hyperskill/hstest/outcomes/Outcome.java @@ -1,5 +1,6 @@ package org.hyperskill.hstest.outcomes; +import org.hyperskill.hstest.common.Utils; import org.hyperskill.hstest.dynamic.output.OutputHandler; import org.hyperskill.hstest.exception.outcomes.CompilationError; import org.hyperskill.hstest.exception.outcomes.ErrorWithFeedback; @@ -42,18 +43,20 @@ public final String toString() { String whenErrorHappened; if (testNumber == 0) { whenErrorHappened = " during testing"; - } else { + } else if (testNumber > 0) { whenErrorHappened = " in test #" + testNumber; + } else { + whenErrorHappened = ""; } String result = getType() + whenErrorHappened; if (!errorText.isEmpty()) { - result += "\n\n" + errorText.trim(); + result += "\n\n" + Utils.cleanText(errorText.trim()); } if (!stackTrace.isEmpty()) { - result += "\n\n" + stackTrace.trim(); + result += "\n\n" + Utils.cleanText(stackTrace.trim()); } String fullOut = OutputHandler.getDynamicOutput(); diff --git a/src/main/java/org/hyperskill/hstest/stage/StageTest.java b/src/main/java/org/hyperskill/hstest/stage/StageTest.java index 37b18e21..e2686794 100644 --- a/src/main/java/org/hyperskill/hstest/stage/StageTest.java +++ b/src/main/java/org/hyperskill/hstest/stage/StageTest.java @@ -1,6 +1,8 @@ package org.hyperskill.hstest.stage; import lombok.Getter; +import org.hyperskill.hstest.common.FileUtils; +import org.hyperskill.hstest.common.ReflectionUtils; import org.hyperskill.hstest.dynamic.ClassSearcher; import org.hyperskill.hstest.dynamic.SystemHandler; import org.hyperskill.hstest.dynamic.output.OutputHandler; @@ -20,7 +22,6 @@ import org.junit.runner.JUnitCore; import org.junit.runner.Result; -import java.io.IOException; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.List; @@ -34,7 +35,7 @@ public abstract class StageTest { - protected TestRunner runner = new AsyncDynamicTestingRunner(); + protected TestRunner runner = null; protected AttachType attach = null; protected String source = null; @@ -43,6 +44,7 @@ public abstract class StageTest { static int currTestGlobal = 0; public static final String LIB_TEST_PACKAGE = "outcomes.separate_package."; + private boolean isTests = false; public StageTest() { this(""); @@ -73,7 +75,7 @@ public StageTest(Class testedClass) { } private TestRunner initRunner() { - for (var folder : walkUserFiles(".")) { + for (var folder : walkUserFiles(FileUtils.cwd())) { for (var file : folder.getFiles()) { if (file.getName().endsWith(".go")) { return new AsyncDynamicTestingRunner(GoExecutor.class); @@ -130,6 +132,12 @@ public final void start() { boolean needTearDown = false; try { SystemHandler.setUp(); + + if (ReflectionUtils.isTests(this)) { + isTests = true; + ReflectionUtils.setupCwd(this); + } + List testRuns = initTests(); for (TestRun testRun : testRuns) { diff --git a/src/main/java/org/hyperskill/hstest/testing/ProcessWrapper.java b/src/main/java/org/hyperskill/hstest/testing/ProcessWrapper.java index 0e1021f1..f1be3d60 100644 --- a/src/main/java/org/hyperskill/hstest/testing/ProcessWrapper.java +++ b/src/main/java/org/hyperskill/hstest/testing/ProcessWrapper.java @@ -2,6 +2,7 @@ import lombok.Getter; import lombok.Setter; +import org.hyperskill.hstest.common.FileUtils; import org.hyperskill.hstest.dynamic.security.ExitException; import org.hyperskill.hstest.exception.outcomes.UnexpectedError; import org.hyperskill.hstest.stage.StageTest; @@ -11,8 +12,12 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Arrays; import java.util.LinkedList; import java.util.List; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicInteger; import static org.hyperskill.hstest.common.Utils.sleep; @@ -32,14 +37,13 @@ public class ProcessWrapper { private final AtomicInteger pipesWatching = new AtomicInteger(0); private boolean terminated = false; - private final List cpuLoadHistory = new LinkedList<>(); + private final Queue cpuLoadHistory = new ConcurrentLinkedQueue<>(); private final int cpuLoadHistoryMax = 2; - private final List outputDiffHistory = new LinkedList<>(); + private final Queue outputDiffHistory = new ConcurrentLinkedQueue<>(); private final int outputDiffHistoryMax = 2; private boolean initialIdleWait = true; - private final int initialIdleWaitMax = 10; @Getter @Setter boolean checkEarlyFinish = false; @Getter @Setter boolean registerOutput = true; @@ -73,6 +77,7 @@ public String getStderr() { public void provideInput(ByteArrayOutputStream input) { try { stdin.write(input.toByteArray()); + stdin.flush(); } catch (IOException e) { StageTest.getCurrTestRun().setErrorInTest( new UnexpectedError("Can't provide input to the process\n" + command)); @@ -86,8 +91,13 @@ public ProcessWrapper start() { } try { - process = new ProcessBuilder(args) - .directory(new File(System.getProperty("user.dir")).getAbsoluteFile()) + List fullArgs = new ArrayList<>(); + fullArgs.add("cmd"); + fullArgs.add("/c"); + fullArgs.addAll(List.of(args)); + + process = new ProcessBuilder(fullArgs) + .directory(new File(FileUtils.cwd())) .start(); stdin = process.getOutputStream(); @@ -203,16 +213,8 @@ private void checkCpuLoad() { cpuLoadHistory.add(currCpuLoad); - if (initialIdleWait && currCpuTime != 0) { - initialIdleWait = false; - } - - if (initialIdleWait && cpuLoadHistory.size() > initialIdleWaitMax) { - initialIdleWait = false; - } - if (!initialIdleWait && cpuLoadHistory.size() > cpuLoadHistoryMax) { - cpuLoadHistory.remove(0); + cpuLoadHistory.remove(); } sleep(10); @@ -230,7 +232,7 @@ private void checkOutput() { outputDiffHistory.add(diff); if (outputDiffHistory.size() > outputDiffHistoryMax) { - outputDiffHistory.remove(0); + outputDiffHistory.remove(); } if (initialIdleWait && diff > 0) { diff --git a/src/main/java/org/hyperskill/hstest/testing/execution/process/GoExecutor.java b/src/main/java/org/hyperskill/hstest/testing/execution/process/GoExecutor.java index 3f87bcf9..154d8341 100644 --- a/src/main/java/org/hyperskill/hstest/testing/execution/process/GoExecutor.java +++ b/src/main/java/org/hyperskill/hstest/testing/execution/process/GoExecutor.java @@ -17,7 +17,7 @@ public class GoExecutor extends ProcessExecutor { private final String executable; private final String filename; - protected GoExecutor(String sourceName) { + public GoExecutor(String sourceName) { super(new GoSearcher().find(sourceName)); var fileName = runnable.getFile().getName(); @@ -36,7 +36,7 @@ protected GoExecutor(String sourceName) { @Override protected List compilationCommand() { - return List.of("go", "build", runnable.getFile().getAbsolutePath()); + return List.of("go", "build", runnable.getFile().getName()); } @Override diff --git a/src/main/java/org/hyperskill/hstest/testing/execution/process/JavaExecutor.java b/src/main/java/org/hyperskill/hstest/testing/execution/process/JavaExecutor.java index 97b52cd8..c495e612 100644 --- a/src/main/java/org/hyperskill/hstest/testing/execution/process/JavaExecutor.java +++ b/src/main/java/org/hyperskill/hstest/testing/execution/process/JavaExecutor.java @@ -7,7 +7,7 @@ public class JavaExecutor extends ProcessExecutor { - protected JavaExecutor(RunnableFile runnable) { + public JavaExecutor(RunnableFile runnable) { super(runnable); } diff --git a/src/main/java/org/hyperskill/hstest/testing/execution/process/JavascriptExecutor.java b/src/main/java/org/hyperskill/hstest/testing/execution/process/JavascriptExecutor.java index c1b2fe98..107a985d 100644 --- a/src/main/java/org/hyperskill/hstest/testing/execution/process/JavascriptExecutor.java +++ b/src/main/java/org/hyperskill/hstest/testing/execution/process/JavascriptExecutor.java @@ -8,7 +8,7 @@ import java.util.List; public class JavascriptExecutor extends ProcessExecutor { - protected JavascriptExecutor(String sourceName) { + public JavascriptExecutor(String sourceName) { super(new JavascriptSearcher().find(sourceName)); } diff --git a/src/main/java/org/hyperskill/hstest/testing/execution/process/PythonExecutor.java b/src/main/java/org/hyperskill/hstest/testing/execution/process/PythonExecutor.java index 6e452618..ced97401 100644 --- a/src/main/java/org/hyperskill/hstest/testing/execution/process/PythonExecutor.java +++ b/src/main/java/org/hyperskill/hstest/testing/execution/process/PythonExecutor.java @@ -8,7 +8,7 @@ import java.util.List; public class PythonExecutor extends ProcessExecutor { - protected PythonExecutor(String sourceName) { + public PythonExecutor(String sourceName) { super(new PythonSearcher().find(sourceName)); } diff --git a/src/main/java/org/hyperskill/hstest/testing/execution/searcher/BaseSearcher.java b/src/main/java/org/hyperskill/hstest/testing/execution/searcher/BaseSearcher.java index da134be0..cf20b218 100644 --- a/src/main/java/org/hyperskill/hstest/testing/execution/searcher/BaseSearcher.java +++ b/src/main/java/org/hyperskill/hstest/testing/execution/searcher/BaseSearcher.java @@ -207,7 +207,7 @@ private static class ParsedSource { } public RunnableFile find(String source) { - if (source == null) { + if (source == null || source.isEmpty()) { return search(null); } diff --git a/src/main/java/org/hyperskill/hstest/testing/runner/AsyncDynamicTestingRunner.java b/src/main/java/org/hyperskill/hstest/testing/runner/AsyncDynamicTestingRunner.java index 1643deac..e02f0f57 100644 --- a/src/main/java/org/hyperskill/hstest/testing/runner/AsyncDynamicTestingRunner.java +++ b/src/main/java/org/hyperskill/hstest/testing/runner/AsyncDynamicTestingRunner.java @@ -7,6 +7,7 @@ import org.hyperskill.hstest.exception.testing.TestedProgramFinishedEarly; import org.hyperskill.hstest.exception.testing.TestedProgramThrewException; import org.hyperskill.hstest.exception.testing.TimeLimitException; +import org.hyperskill.hstest.stage.StageTest; import org.hyperskill.hstest.testcase.CheckResult; import org.hyperskill.hstest.testcase.TestCase; import org.hyperskill.hstest.testing.TestRun; @@ -110,4 +111,11 @@ public CheckResult test(TestRun testRun) { return result; } + + @Override + public void tearDown(TestCase testCase) { + for (var program : StageTest.getCurrTestRun().getTestedPrograms()) { + program.getProgramExecutor().tearDown(); + } + } } From 811623aa9f1d4d0b26ac90730cb5ab2262d4221c Mon Sep 17 00:00:00 2001 From: aaaaaa2493 Date: Thu, 28 Oct 2021 03:21:46 +0300 Subject: [PATCH 13/55] Add tests on various Go, Python, JavaScript solutions --- .../projects/go/coffee_machine/stage1/main.go | 15 + .../stage1/test/CoffeeMachineTestGo1.java | 34 ++ .../go/coffee_machine/stage1_ce/main.go | 9 + .../test/CoffeeMachineTestGo1Ce.java | 41 +++ .../go/coffee_machine/stage1_ex/main.go | 5 + .../test/CoffeeMachineTestGo1Ex.java | 43 +++ .../go/coffee_machine/stage1_wa/main.go | 9 + .../test/CoffeeMachineTestGo1Wa.java | 46 +++ .../projects/go/coffee_machine/stage2/main.go | 15 + .../stage2/test/CoffeeMachineTestGo2.java | 101 +++++ .../projects/go/coffee_machine/stage3/main.go | 36 ++ .../stage3/test/CoffeeMachineTestGo3.java | 167 +++++++++ .../projects/go/coffee_machine/stage4/main.go | 84 +++++ .../stage4/test/CoffeeMachineTestGo4.java | 346 ++++++++++++++++++ .../projects/go/coffee_machine/stage5/main.go | 95 +++++ .../stage5/test/CoffeeMachineTestGo5.java | 263 +++++++++++++ .../javascript/coffee_machine/stage1/main.js | 8 + .../stage1/test/CoffeeMachineTestJs1.java | 34 ++ .../coffee_machine/stage1_ce/main.js | 8 + .../test/CoffeeMachineTestJs1Ce.java | 42 +++ .../coffee_machine/stage1_ex/main.js | 2 + .../test/CoffeeMachineTestJs1Ex.java | 45 +++ .../coffee_machine/stage1_wa/main.js | 1 + .../test/CoffeeMachineTestJs1Wa.java | 46 +++ .../javascript/coffee_machine/stage2/main.js | 10 + .../stage2/node_modules/sync-input/index.js | 35 ++ .../stage2/test/CoffeeMachineTestJs2.java | 101 +++++ .../javascript/coffee_machine/stage3/main.js | 25 ++ .../stage3/node_modules/sync-input/index.js | 35 ++ .../stage3/test/CoffeeMachineTestJs3.java | 167 +++++++++ .../javascript/coffee_machine/stage4/main.js | 70 ++++ .../stage4/node_modules/sync-input/index.js | 35 ++ .../stage4/test/CoffeeMachineTestJs4.java | 346 ++++++++++++++++++ .../javascript/coffee_machine/stage5/main.js | 77 ++++ .../stage5/node_modules/sync-input/index.js | 35 ++ .../stage5/test/CoffeeMachineTestJs5.java | 263 +++++++++++++ .../stage1/machine/coffee_machine.py | 8 + .../stage1/test/CoffeeMachineTestPy1.java | 34 ++ .../stage1_ce/machine/coffee_machine.py | 8 + .../test/CoffeeMachineTestPy1Ce.java | 50 +++ .../stage1_ex/machine/coffee_machine.py | 2 + .../test/CoffeeMachineTestPy1Ex.java | 47 +++ .../stage1_wa/machine/coffee_machine.py | 2 + .../test/CoffeeMachineTestPy1Wa.java | 46 +++ .../stage2/machine/coffee_machine.py | 18 + .../stage2/test/CoffeeMachineTestPy2.java | 101 +++++ .../stage3/machine/coffee_machine.py | 29 ++ .../stage3/test/CoffeeMachineTestPy3.java | 167 +++++++++ .../stage4/machine/coffee_machine.py | 47 +++ .../stage4/test/CoffeeMachineTestPy4.java | 346 ++++++++++++++++++ .../stage5/machine/coffee_machine.py | 113 ++++++ .../stage5/test/CoffeeMachineTestPy5.java | 263 +++++++++++++ 52 files changed, 3975 insertions(+) create mode 100644 src/test/java/projects/go/coffee_machine/stage1/main.go create mode 100644 src/test/java/projects/go/coffee_machine/stage1/test/CoffeeMachineTestGo1.java create mode 100644 src/test/java/projects/go/coffee_machine/stage1_ce/main.go create mode 100644 src/test/java/projects/go/coffee_machine/stage1_ce/test/CoffeeMachineTestGo1Ce.java create mode 100644 src/test/java/projects/go/coffee_machine/stage1_ex/main.go create mode 100644 src/test/java/projects/go/coffee_machine/stage1_ex/test/CoffeeMachineTestGo1Ex.java create mode 100644 src/test/java/projects/go/coffee_machine/stage1_wa/main.go create mode 100644 src/test/java/projects/go/coffee_machine/stage1_wa/test/CoffeeMachineTestGo1Wa.java create mode 100644 src/test/java/projects/go/coffee_machine/stage2/main.go create mode 100644 src/test/java/projects/go/coffee_machine/stage2/test/CoffeeMachineTestGo2.java create mode 100644 src/test/java/projects/go/coffee_machine/stage3/main.go create mode 100644 src/test/java/projects/go/coffee_machine/stage3/test/CoffeeMachineTestGo3.java create mode 100644 src/test/java/projects/go/coffee_machine/stage4/main.go create mode 100644 src/test/java/projects/go/coffee_machine/stage4/test/CoffeeMachineTestGo4.java create mode 100644 src/test/java/projects/go/coffee_machine/stage5/main.go create mode 100644 src/test/java/projects/go/coffee_machine/stage5/test/CoffeeMachineTestGo5.java create mode 100644 src/test/java/projects/javascript/coffee_machine/stage1/main.js create mode 100644 src/test/java/projects/javascript/coffee_machine/stage1/test/CoffeeMachineTestJs1.java create mode 100644 src/test/java/projects/javascript/coffee_machine/stage1_ce/main.js create mode 100644 src/test/java/projects/javascript/coffee_machine/stage1_ce/test/CoffeeMachineTestJs1Ce.java create mode 100644 src/test/java/projects/javascript/coffee_machine/stage1_ex/main.js create mode 100644 src/test/java/projects/javascript/coffee_machine/stage1_ex/test/CoffeeMachineTestJs1Ex.java create mode 100644 src/test/java/projects/javascript/coffee_machine/stage1_wa/main.js create mode 100644 src/test/java/projects/javascript/coffee_machine/stage1_wa/test/CoffeeMachineTestJs1Wa.java create mode 100644 src/test/java/projects/javascript/coffee_machine/stage2/main.js create mode 100644 src/test/java/projects/javascript/coffee_machine/stage2/node_modules/sync-input/index.js create mode 100644 src/test/java/projects/javascript/coffee_machine/stage2/test/CoffeeMachineTestJs2.java create mode 100644 src/test/java/projects/javascript/coffee_machine/stage3/main.js create mode 100644 src/test/java/projects/javascript/coffee_machine/stage3/node_modules/sync-input/index.js create mode 100644 src/test/java/projects/javascript/coffee_machine/stage3/test/CoffeeMachineTestJs3.java create mode 100644 src/test/java/projects/javascript/coffee_machine/stage4/main.js create mode 100644 src/test/java/projects/javascript/coffee_machine/stage4/node_modules/sync-input/index.js create mode 100644 src/test/java/projects/javascript/coffee_machine/stage4/test/CoffeeMachineTestJs4.java create mode 100644 src/test/java/projects/javascript/coffee_machine/stage5/main.js create mode 100644 src/test/java/projects/javascript/coffee_machine/stage5/node_modules/sync-input/index.js create mode 100644 src/test/java/projects/javascript/coffee_machine/stage5/test/CoffeeMachineTestJs5.java create mode 100644 src/test/java/projects/python/coffee_machine/stage1/machine/coffee_machine.py create mode 100644 src/test/java/projects/python/coffee_machine/stage1/test/CoffeeMachineTestPy1.java create mode 100644 src/test/java/projects/python/coffee_machine/stage1_ce/machine/coffee_machine.py create mode 100644 src/test/java/projects/python/coffee_machine/stage1_ce/test/CoffeeMachineTestPy1Ce.java create mode 100644 src/test/java/projects/python/coffee_machine/stage1_ex/machine/coffee_machine.py create mode 100644 src/test/java/projects/python/coffee_machine/stage1_ex/test/CoffeeMachineTestPy1Ex.java create mode 100644 src/test/java/projects/python/coffee_machine/stage1_wa/machine/coffee_machine.py create mode 100644 src/test/java/projects/python/coffee_machine/stage1_wa/test/CoffeeMachineTestPy1Wa.java create mode 100644 src/test/java/projects/python/coffee_machine/stage2/machine/coffee_machine.py create mode 100644 src/test/java/projects/python/coffee_machine/stage2/test/CoffeeMachineTestPy2.java create mode 100644 src/test/java/projects/python/coffee_machine/stage3/machine/coffee_machine.py create mode 100644 src/test/java/projects/python/coffee_machine/stage3/test/CoffeeMachineTestPy3.java create mode 100644 src/test/java/projects/python/coffee_machine/stage4/machine/coffee_machine.py create mode 100644 src/test/java/projects/python/coffee_machine/stage4/test/CoffeeMachineTestPy4.java create mode 100644 src/test/java/projects/python/coffee_machine/stage5/machine/coffee_machine.py create mode 100644 src/test/java/projects/python/coffee_machine/stage5/test/CoffeeMachineTestPy5.java diff --git a/src/test/java/projects/go/coffee_machine/stage1/main.go b/src/test/java/projects/go/coffee_machine/stage1/main.go new file mode 100644 index 00000000..ad7cdc99 --- /dev/null +++ b/src/test/java/projects/go/coffee_machine/stage1/main.go @@ -0,0 +1,15 @@ +package main + +import ( + "fmt" +) + +func main() { + fmt.Println(`Starting to make a coffee +Grinding coffee beans +Boiling water +Mixing boiled water with crushed coffee beans +Pouring coffee into the cup +Pouring some milk into the cup +Coffee is ready!`) +} diff --git a/src/test/java/projects/go/coffee_machine/stage1/test/CoffeeMachineTestGo1.java b/src/test/java/projects/go/coffee_machine/stage1/test/CoffeeMachineTestGo1.java new file mode 100644 index 00000000..c56a6548 --- /dev/null +++ b/src/test/java/projects/go/coffee_machine/stage1/test/CoffeeMachineTestGo1.java @@ -0,0 +1,34 @@ +package projects.go.coffee_machine.stage1.test; + +import org.hyperskill.hstest.stage.StageTest; +import org.hyperskill.hstest.testcase.CheckResult; +import org.hyperskill.hstest.testcase.TestCase; + +import java.util.List; + + +public class CoffeeMachineTestGo1 extends StageTest { + + @Override + public List> generate() { + return List.of( + new TestCase() + .setInput("") + .setAttach("Starting to make a coffee\n" + + "Grinding coffee beans\n" + + "Boiling water\n" + + "Mixing boiled water with crushed coffee beans\n" + + "Pouring coffee into the cup\n" + + "Pouring some milk into the cup\n" + + "Coffee is ready!") + ); + } + + @Override + public CheckResult check(String reply, String clue) { + boolean isCorrect = reply.trim().equals(clue.trim()); + return new CheckResult(isCorrect, + "You should make coffee exactly " + + "like in the example"); + } +} diff --git a/src/test/java/projects/go/coffee_machine/stage1_ce/main.go b/src/test/java/projects/go/coffee_machine/stage1_ce/main.go new file mode 100644 index 00000000..a82c2ff7 --- /dev/null +++ b/src/test/java/projects/go/coffee_machine/stage1_ce/main.go @@ -0,0 +1,9 @@ +package main + +import ( + "fmt" +) + +func main() { + Println(`12123123`) +} diff --git a/src/test/java/projects/go/coffee_machine/stage1_ce/test/CoffeeMachineTestGo1Ce.java b/src/test/java/projects/go/coffee_machine/stage1_ce/test/CoffeeMachineTestGo1Ce.java new file mode 100644 index 00000000..3f6661d9 --- /dev/null +++ b/src/test/java/projects/go/coffee_machine/stage1_ce/test/CoffeeMachineTestGo1Ce.java @@ -0,0 +1,41 @@ +package projects.go.coffee_machine.stage1_ce.test; + +import org.hyperskill.hstest.testcase.CheckResult; +import org.hyperskill.hstest.testcase.TestCase; +import outcomes.base.ContainsMessage; +import outcomes.base.UserErrorTest; + +import java.util.List; + + +public class CoffeeMachineTestGo1Ce extends UserErrorTest { + + @ContainsMessage + String msg = "Compilation error\n" + + "\n" + + ".\\main.go:4:2: imported and not used: \"fmt\"\n" + + ".\\main.go:8:2: undefined: Println"; + + @Override + public List> generate() { + return List.of( + new TestCase() + .setInput("") + .setAttach("Starting to make a coffee\n" + + "Grinding coffee beans\n" + + "Boiling water\n" + + "Mixing boiled water with crushed coffee beans\n" + + "Pouring coffee into the cup\n" + + "Pouring some milk into the cup\n" + + "Coffee is ready!") + ); + } + + @Override + public CheckResult check(String reply, String clue) { + boolean isCorrect = reply.trim().equals(clue.trim()); + return new CheckResult(isCorrect, + "You should make coffee exactly " + + "like in the example"); + } +} diff --git a/src/test/java/projects/go/coffee_machine/stage1_ex/main.go b/src/test/java/projects/go/coffee_machine/stage1_ex/main.go new file mode 100644 index 00000000..68f90612 --- /dev/null +++ b/src/test/java/projects/go/coffee_machine/stage1_ex/main.go @@ -0,0 +1,5 @@ +package main + +func main() { + panic("unreachable") +} diff --git a/src/test/java/projects/go/coffee_machine/stage1_ex/test/CoffeeMachineTestGo1Ex.java b/src/test/java/projects/go/coffee_machine/stage1_ex/test/CoffeeMachineTestGo1Ex.java new file mode 100644 index 00000000..30b022fe --- /dev/null +++ b/src/test/java/projects/go/coffee_machine/stage1_ex/test/CoffeeMachineTestGo1Ex.java @@ -0,0 +1,43 @@ +package projects.go.coffee_machine.stage1_ex.test; + +import org.hyperskill.hstest.testcase.CheckResult; +import org.hyperskill.hstest.testcase.TestCase; +import outcomes.base.ContainsMessage; +import outcomes.base.UserErrorTest; + +import java.util.List; + + +public class CoffeeMachineTestGo1Ex extends UserErrorTest { + + @ContainsMessage + String msg = "Exception in test #1\n" + + "\n" + + "panic: unreachable\n" + + "\n" + + "goroutine 1 [running]:\n" + + "main.main()"; + + @Override + public List> generate() { + return List.of( + new TestCase() + .setInput("") + .setAttach("Starting to make a coffee\n" + + "Grinding coffee beans\n" + + "Boiling water\n" + + "Mixing boiled water with crushed coffee beans\n" + + "Pouring coffee into the cup\n" + + "Pouring some milk into the cup\n" + + "Coffee is ready!") + ); + } + + @Override + public CheckResult check(String reply, String clue) { + boolean isCorrect = reply.trim().equals(clue.trim()); + return new CheckResult(isCorrect, + "You should make coffee exactly " + + "like in the example"); + } +} diff --git a/src/test/java/projects/go/coffee_machine/stage1_wa/main.go b/src/test/java/projects/go/coffee_machine/stage1_wa/main.go new file mode 100644 index 00000000..dec08d9f --- /dev/null +++ b/src/test/java/projects/go/coffee_machine/stage1_wa/main.go @@ -0,0 +1,9 @@ +package main + +import ( + "fmt" +) + +func main() { + fmt.Println(`12123123`) +} diff --git a/src/test/java/projects/go/coffee_machine/stage1_wa/test/CoffeeMachineTestGo1Wa.java b/src/test/java/projects/go/coffee_machine/stage1_wa/test/CoffeeMachineTestGo1Wa.java new file mode 100644 index 00000000..a9c540d7 --- /dev/null +++ b/src/test/java/projects/go/coffee_machine/stage1_wa/test/CoffeeMachineTestGo1Wa.java @@ -0,0 +1,46 @@ +package projects.go.coffee_machine.stage1_wa.test; + +import org.hyperskill.hstest.testcase.CheckResult; +import org.hyperskill.hstest.testcase.TestCase; +import outcomes.base.ContainsMessage; +import outcomes.base.UserErrorTest; + +import java.util.List; + + +public class CoffeeMachineTestGo1Wa extends UserErrorTest { + + @ContainsMessage + String msg = "Wrong answer in test #1\n" + + "\n" + + "You should make coffee exactly like in the example\n" + + "\n" + + "Please find below the output of your program during this failed test.\n" + + "\n" + + "---\n" + + "\n" + + "12123123"; + + @Override + public List> generate() { + return List.of( + new TestCase() + .setInput("") + .setAttach("Starting to make a coffee\n" + + "Grinding coffee beans\n" + + "Boiling water\n" + + "Mixing boiled water with crushed coffee beans\n" + + "Pouring coffee into the cup\n" + + "Pouring some milk into the cup\n" + + "Coffee is ready!") + ); + } + + @Override + public CheckResult check(String reply, String clue) { + boolean isCorrect = reply.trim().equals(clue.trim()); + return new CheckResult(isCorrect, + "You should make coffee exactly " + + "like in the example"); + } +} diff --git a/src/test/java/projects/go/coffee_machine/stage2/main.go b/src/test/java/projects/go/coffee_machine/stage2/main.go new file mode 100644 index 00000000..3c2bc1ca --- /dev/null +++ b/src/test/java/projects/go/coffee_machine/stage2/main.go @@ -0,0 +1,15 @@ +package main + +import ( + "fmt" +) + +func main() { + var cups int + fmt.Print("Write how many cups of coffee you will need: ") + fmt.Scanf("%d\n", &cups) + fmt.Printf(`For %d cups of coffee you will need: +%d ml of water +%d ml of milk +%d g of coffee beans`, cups, cups*200, cups*50, cups*15) +} diff --git a/src/test/java/projects/go/coffee_machine/stage2/test/CoffeeMachineTestGo2.java b/src/test/java/projects/go/coffee_machine/stage2/test/CoffeeMachineTestGo2.java new file mode 100644 index 00000000..16e5c484 --- /dev/null +++ b/src/test/java/projects/go/coffee_machine/stage2/test/CoffeeMachineTestGo2.java @@ -0,0 +1,101 @@ +package projects.go.coffee_machine.stage2.test; + +import org.hyperskill.hstest.stage.StageTest; +import org.hyperskill.hstest.testcase.CheckResult; +import org.hyperskill.hstest.testcase.TestCase; + +import java.util.List; + + +public class CoffeeMachineTestGo2 extends StageTest { + + @Override + public List> generate() { + return List.of( + new TestCase() + .setInput("25") + .setAttach("25"), + + new TestCase() + .setInput("125") + .setAttach("125"), + + new TestCase() + .setInput("1") + .setAttach("1"), + + new TestCase() + .setInput("123") + .setAttach("123") + ); + } + + @Override + public CheckResult check(String reply, String clue) { + String[] lines = reply.split("\\n"); + if (lines.length < 3) { + return new CheckResult(false, + "Output contains less than 3 lines, but should output at least 3 lines"); + } + String[] last3Lines = { + lines[lines.length - 3], + lines[lines.length - 2], + lines[lines.length - 1] + }; + + int cups = Integer.parseInt(clue); + boolean water = false, milk = false, beans = false; + + for(String line : last3Lines) { + line = line.toLowerCase(); + + if(line.contains("milk")) { + milk = line.contains(Integer.toString(cups * 50)); + if (!milk) { + return new CheckResult(false, + "For the input " + clue + " your program output:\n\"" + + line + "\"\nbut the amount of milk should be " + (cups * 50)); + } + + } else if(line.contains("water")) { + water = line.contains(Integer.toString(cups * 200)); + if (!water) { + return new CheckResult(false, + "For the input " + clue + " your program output:\n" + + line + "\nbut the amount of water should be " + (cups * 200)); + } + + } else if(line.contains("beans")) { + beans = line.contains(Integer.toString(cups * 15)); + if (!beans) { + return new CheckResult(false, + "For the input " + clue + " your program output:\n" + + line + "\nbut the amount of beans should be " + (cups * 15)); + } + + + } else { + return new CheckResult(false, + "One of the last 3 lines " + + "doesn't contain \"milk\", \"water\" or \"beans\""); + } + } + + if (!water) { + return new CheckResult(false, + "There is no line with amount of water"); + } + + if (!milk) { + return new CheckResult(false, + "There is no line with amount of milk"); + } + + if (!beans) { + return new CheckResult(false, + "There is no line with amount of coffee beans"); + } + + return CheckResult.correct(); + } +} diff --git a/src/test/java/projects/go/coffee_machine/stage3/main.go b/src/test/java/projects/go/coffee_machine/stage3/main.go new file mode 100644 index 00000000..3d5a53fb --- /dev/null +++ b/src/test/java/projects/go/coffee_machine/stage3/main.go @@ -0,0 +1,36 @@ +package main + +import "fmt" + +func main() { + var cups, water, milk, beans, needs int + + fmt.Print("Write how many ml of water the coffee machine has: ") + fmt.Scanf("%d\n", &water) + cups = water / 200 + + fmt.Print("Write how many ml of milk the coffee machine has: ") + fmt.Scanf("%d\n", &milk) + if milk/50 < cups { + cups = milk / 50 + } + + fmt.Print("Write how many grams of coffee beans the coffee machine has: ") + fmt.Scanf("%d\n", &beans) + if beans/15 < cups { + cups = beans / 15 + } + + fmt.Print("Write how many cups of coffee you will need: ") + fmt.Scanf("%d\n", &needs) + if needs == cups { + fmt.Println("Yes, I can make that amount of coffee") + } else if needs < cups { + fmt.Printf( + "Yes, I can make that amount of coffee (and even %d more than that)\n", + cups-needs, + ) + } else { + fmt.Printf("No, I can make only %d cup(s) of coffee\n", cups) + } +} diff --git a/src/test/java/projects/go/coffee_machine/stage3/test/CoffeeMachineTestGo3.java b/src/test/java/projects/go/coffee_machine/stage3/test/CoffeeMachineTestGo3.java new file mode 100644 index 00000000..d2709353 --- /dev/null +++ b/src/test/java/projects/go/coffee_machine/stage3/test/CoffeeMachineTestGo3.java @@ -0,0 +1,167 @@ +package projects.go.coffee_machine.stage3.test; + +import org.hyperskill.hstest.stage.StageTest; +import org.hyperskill.hstest.testcase.CheckResult; +import org.hyperskill.hstest.testcase.TestCase; + +import java.util.List; + + +class TestClue { + boolean cond; + int num; + boolean showTests; + TestClue(boolean c, int n, boolean showTests) { + cond = c; + num = n; + this.showTests = showTests; + } +} + +public class CoffeeMachineTestGo3 extends StageTest { + + @Override + public List> generate() { + return List.of( + new TestCase() + .setAttach(new TestClue(true, 0, true)) + .setInput("300\n65\n111\n1"), + + new TestCase() + .setAttach(new TestClue(true, 2, true)) + .setInput("600\n153\n100\n1"), + + new TestCase() + .setAttach(new TestClue(true, 2, true)) + .setInput("1400\n150\n100\n1"), + + new TestCase() + .setAttach(new TestClue(true, 2, true)) + .setInput("1400\n1500\n45\n1"), + + new TestCase() + .setAttach(new TestClue(false, 2, true)) + .setInput("599\n250\n200\n10"), + + new TestCase() + .setAttach(new TestClue(true, 867, true)) + .setInput( "345640\n43423\n23234\n1"), + + new TestCase() + .setAttach(new TestClue(false, 1548, true)) + .setInput("345640\n434230\n23234\n19246"), + + new TestCase() + .setAttach(new TestClue(false, 172, true)) + .setInput( "34564\n43423\n23234\n19246"), + + new TestCase() + .setAttach(new TestClue(true, 0, false)) + .setInput("399\n112\n111\n1"), + + new TestCase() + .setAttach(new TestClue(true, 3, false)) + .setInput("2400\n249\n100\n1"), + + new TestCase() + .setAttach(new TestClue(true, 1, false)) + .setInput("1400\n1500\n44\n1"), + + new TestCase() + .setAttach(new TestClue(false, 2, false)) + .setInput("500\n250\n200\n10"), + + new TestCase() + .setAttach(new TestClue(true, 171, false)) + .setInput("34564\n43423\n23234\n1"), + + new TestCase() + .setAttach(new TestClue(true, 1547, false)) + .setInput("345640\n434230\n23234\n1"), + + new TestCase() + .setAttach(new TestClue(false, 868, false)) + .setInput("345640\n43423\n23234\n19246") + + ); + } + + @Override + public CheckResult check(String reply, TestClue clue) { + String[] lines = reply.trim().split(":"); + + if (lines.length <= 1) { + return CheckResult.wrong("Looks like you didn't print anything!"); + } + + String userOutput = lines[lines.length - 1].trim(); + String loweredOutput = userOutput.toLowerCase(); + boolean ans = clue.cond; + int amount = clue.num; + if (ans) { + if (amount > 0) { + boolean isCorrect = + loweredOutput.contains(Integer.toString(amount)) + && loweredOutput.contains("yes"); + + if (isCorrect) { + return CheckResult.correct(); + } else { + + String rightOutput = + "Yes, I can make that amount of coffee" + + " (and even " + amount + " more than that)"; + + if (clue.showTests) { + return new CheckResult(false, + "Your output:\n" + + userOutput + + "\nRight output:\n" + + rightOutput); + } else { + return CheckResult.wrong(""); + } + } + } + + String rightOutput = + "Yes, I can make that amount of coffee"; + + if (loweredOutput.equals(rightOutput.toLowerCase())) { + return CheckResult.correct(); + } else { + if (clue.showTests) { + return new CheckResult(false, + "Your output:\n" + + userOutput + + "\nRight output:\n" + + rightOutput); + } else { + return CheckResult.wrong(""); + } + } + + } else { + boolean cond1 = loweredOutput.contains("no"); + boolean cond2 = loweredOutput.contains(Integer.toString(amount)); + + if (cond1 && cond2) { + return CheckResult.correct(); + } else { + + String rightOutput = "No, I can make only " + + amount +" cup(s) of coffee"; + + if (clue.showTests) { + return new CheckResult(false, + "Your output:\n" + + userOutput + + "\nRight output:\n" + + rightOutput); + } else { + return CheckResult.wrong(""); + } + } + } + } +} diff --git a/src/test/java/projects/go/coffee_machine/stage4/main.go b/src/test/java/projects/go/coffee_machine/stage4/main.go new file mode 100644 index 00000000..10c91fe3 --- /dev/null +++ b/src/test/java/projects/go/coffee_machine/stage4/main.go @@ -0,0 +1,84 @@ +package main + +import "fmt" + +func main() { + var ( + money = 550 + water = 400 + milk = 540 + beans = 120 + cups = 9 + choice int + ) + + printState := func() { + fmt.Println("The coffee machine has:") + fmt.Printf( + "%d of water\n%d of milk\n%d of coffee beans\n%d of disposable cups\n$%d of money\n", + water, milk, beans, cups, money, + ) + } + + takeMoney := func() { + fmt.Printf("I gave you $%d\n", money) + money = 0 + } + + sellCoffee := func() { + fmt.Print("What do you want to buy? 1 - espresso, 2 - latte, 3 - cappuccino: ") + fmt.Scanf("%d\n", &choice) + switch choice { + case 1: + money += 4 + water -= 250 + beans -= 16 + cups -= 1 + case 2: + money += 7 + water -= 350 + milk -= 75 + beans -= 20 + cups -= 1 + case 3: + money += 6 + water -= 200 + milk -= 100 + beans -= 12 + cups -= 1 + } + } + + fillMachine := func() { + var next int + fmt.Print("Write how many ml of water do you want to add: ") + fmt.Scanf("%d\n", &next) + water += next + fmt.Print("Write how many ml of milk do you want to add: ") + fmt.Scanf("%d\n", &next) + milk += next + fmt.Print("Write how many grams of coffee beans do you want to add: ") + fmt.Scanf("%d\n", &next) + beans += next + fmt.Print("Write how many disposable cups of coffee do you want to add: ") + fmt.Scanf("%d\n", &next) + cups += next + } + + printState() + + var action string + fmt.Print("\nWrite action (buy, fill, take): ") + fmt.Scanf("%s\n", &action) + switch action { + case "buy": + sellCoffee() + case "fill": + fillMachine() + case "take": + takeMoney() + } + + fmt.Println() + printState() +} diff --git a/src/test/java/projects/go/coffee_machine/stage4/test/CoffeeMachineTestGo4.java b/src/test/java/projects/go/coffee_machine/stage4/test/CoffeeMachineTestGo4.java new file mode 100644 index 00000000..7e057ba0 --- /dev/null +++ b/src/test/java/projects/go/coffee_machine/stage4/test/CoffeeMachineTestGo4.java @@ -0,0 +1,346 @@ +package projects.go.coffee_machine.stage4.test; + +import org.hyperskill.hstest.stage.StageTest; +import org.hyperskill.hstest.testcase.CheckResult; +import org.hyperskill.hstest.testcase.TestCase; + +import java.util.ArrayList; +import java.util.List; + + +class TestClue { + String string; + TestClue(String s) { + string = s; + } +} + +public class CoffeeMachineTestGo4 extends StageTest { + + @Override + public List> generate() { + return List.of( + new TestCase() + .setAttach(new TestClue("take\n")) + .setInput("take\n"), + + new TestCase() + .setAttach(new TestClue("buy\n1\n")) + .setInput("buy\n1\n"), + + new TestCase() + .setAttach(new TestClue("buy\n2\n")) + .setInput("buy\n2\n"), + + new TestCase() + .setAttach(new TestClue("buy\n3\n")) + .setInput("buy\n3\n"), + + new TestCase() + .setAttach(new TestClue("fill\n2001\n510\n101\n21\n")) + .setInput("fill\n2001\n510\n101\n21\n") + ); + } + + @Override + public CheckResult check(String reply, TestClue clue) { + String[] lines = reply.trim().split("\\n"); + + if (lines.length <= 1) { + return CheckResult.wrong("Looks like you didn't print anything!"); + } + + String[] clueLines = clue.string.trim().split("\\n"); + String action = clueLines[0].trim(); + + List milk = new ArrayList<>(); + List water = new ArrayList<>(); + List beans = new ArrayList<>(); + List cups = new ArrayList<>(); + List money = new ArrayList<>(); + + for (String line : lines) { + String[] words = line.split("\\s+"); + if (words.length == 0) { + continue; + } + String firstWord = words[0].replace("$", ""); + int amount; + try { + amount = Integer.parseInt(firstWord); + } + catch (Exception e) { + continue; + } + if (line.contains("milk")) { + milk.add(amount); + } + else if (line.contains("water")) { + water.add(amount); + } + else if (line.contains("beans")) { + beans.add(amount); + } + else if (line.contains("cups")) { + cups.add(amount); + } + else if (line.contains("money")) { + money.add(amount); + } + } + + if (milk.size() != 2) { + return new CheckResult(false, + "There should be two lines with \"milk\", " + + "found: " + milk.size()); + } + + if (water.size() != 2) { + return new CheckResult(false, + "There should be two lines with \"water\", " + + "found: " + water.size()); + } + + if (beans.size() != 2) { + return new CheckResult(false, + "There should be two lines with \"beans\", " + + "found: " + beans.size()); + } + + if (cups.size() != 2) { + return new CheckResult(false, + "There should be two lines with \"cups\", " + + "found: " + cups.size()); + } + + if (money.size() != 2) { + return new CheckResult(false, + "There should be two lines with \"money\", " + + "found: " + money.size()); + } + + + int milk0 = milk.get(0); + int milk1 = milk.get(milk.size() - 1); + + int water0 = water.get(0); + int water1 = water.get(water.size() - 1); + + int beans0 = beans.get(0); + int beans1 = beans.get(beans.size() - 1); + + int cups0 = cups.get(0); + int cups1 = cups.get(cups.size() - 1); + + int money0 = money.get(0); + int money1 = money.get(money.size() - 1); + + if (water0 != 400 || milk0 != 540 || beans0 != 120 + || cups0 != 9 || money0 != 550) { + return new CheckResult(false, + "Initial setup is wrong: " + + "coffee machine should fe filled like " + + "stated in the description"); + } + + int diffWater = water1 - water0; + int diffMilk = milk1 - milk0; + int diffBeans = beans1 - beans0; + int diffCups = cups1 - cups0; + int diffMoney = money1 - money0; + + if (action.equals("take")) { + + if (diffMilk != 0) { + return new CheckResult(false, + "After \"take\" action milk " + + "amount shouldn't be changed"); + } + + if (diffWater != 0) { + return new CheckResult(false, + "After \"take\" action water " + + "amount shouldn't be changed"); + } + + if (diffBeans != 0) { + return new CheckResult(false, + "After \"take\" action beans " + + "amount shouldn't be changed"); + } + + if (diffCups != 0) { + return new CheckResult(false, + "After \"take\" action cups " + + "amount shouldn't be changed"); + } + + if (money1 != 0) { + return new CheckResult(false, + "After \"take\" action money " + + "amount should be zero"); + } + + return CheckResult.correct(); + } + + else if (action.equals("buy")) { + + String option = clueLines[1].trim(); + + if (option.equals("1")) { + + if (diffWater != -250) { + return new CheckResult(false, + "After buying the first option " + + "water amount should be lowered by 250"); + } + + if (diffMilk != 0) { + return new CheckResult(false, + "After buying the first option " + + "milk amount should not be changed"); + } + + if (diffBeans != -16) { + return new CheckResult(false, + "After buying the first option " + + "beans amount should be lowered by 16"); + } + + if (diffCups != -1) { + return new CheckResult(false, + "After buying the first option " + + "cups amount should be lowered by 1"); + } + + if (diffMoney != 4) { + return new CheckResult(false, + "After buying the first option " + + "money amount should be increased by 4"); + } + + return CheckResult.correct(); + + } + + else if (option.equals("2")) { + + if (diffWater != -350) { + return new CheckResult(false, + "After buying the second option " + + "water amount should be lowered by 350"); + } + + if (diffMilk != -75) { + return new CheckResult(false, + "After buying the second option " + + "milk amount should be lowered by 75"); + } + + if (diffBeans != -20) { + return new CheckResult(false, + "After buying the second option " + + "beans amount should be lowered by 20"); + } + + if (diffCups != -1) { + return new CheckResult(false, + "After buying the second option " + + "cups amount should be lowered by 1"); + } + + if (diffMoney != 7) { + return new CheckResult(false, + "After buying the second option " + + "money amount should be increased by 7"); + } + + return CheckResult.correct(); + } + + else if (option.equals("3")) { + + if (diffWater != -200) { + return new CheckResult(false, + "After buying the third option " + + "water amount should be lowered by 350"); + } + + if (diffMilk != -100) { + return new CheckResult(false, + "After buying the third option " + + "milk amount should be lowered by 75"); + } + + if (diffBeans != -12) { + return new CheckResult(false, + "After buying the third option " + + "beans amount should be lowered by 20"); + } + + if (diffCups != -1) { + return new CheckResult(false, + "After buying the third option " + + "cups amount should be lowered by 1"); + } + + if (diffMoney != 6) { + return new CheckResult(false, + "After buying the third option " + + "money amount should be increased by 7"); + } + + return CheckResult.correct(); + } + } + + else if (action.equals("fill")) { + + int water_ = Integer.parseInt(clueLines[1]); + int milk_ = Integer.parseInt(clueLines[2]); + int beans_ = Integer.parseInt(clueLines[3]); + int cups_ = Integer.parseInt(clueLines[4]); + + if (diffMoney != 0) { + return new CheckResult(false, + "After \"fill\" action " + + "money amount should not be changed"); + } + + if (diffWater != water_) { + return new CheckResult(false, + "After \"fill\" action " + + "water amount expected to be increased by " + water_ + + " but was increased by " + diffWater); + } + + if (diffMilk != milk_) { + return new CheckResult(false, + "After \"fill\" action " + + "milk amount expected to be increased by " + milk_ + + " but was increased by " + diffMilk); + } + + if (diffBeans != beans_) { + return new CheckResult(false, + "After \"fill\" action " + + "beans amount expected to be increased by " + beans_ + + " but was increased by " + diffBeans); + } + + if (diffCups != cups_) { + return new CheckResult(false, + "After \"fill\" action " + + "cups amount expected to be increased by " + cups_ + + " but was increased by " + diffCups); + } + + + return CheckResult.correct(); + + } + + throw new RuntimeException("Can't check the answer"); + } +} diff --git a/src/test/java/projects/go/coffee_machine/stage5/main.go b/src/test/java/projects/go/coffee_machine/stage5/main.go new file mode 100644 index 00000000..df09459e --- /dev/null +++ b/src/test/java/projects/go/coffee_machine/stage5/main.go @@ -0,0 +1,95 @@ +package main + +import "fmt" + +func main() { + var ( + money = 550 + water = 400 + milk = 540 + beans = 120 + cups = 9 + choice int + ) + + printState := func() { + fmt.Println("The coffee machine has:") + fmt.Printf( + "%d of water\n%d of milk\n%d of coffee beans\n%d of disposable cups\n$%d of money\n", + water, milk, beans, cups, money, + ) + } + + takeMoney := func() { + fmt.Printf("I gave you $%d\n", money) + money = 0 + } + + sellCoffee := func() { + fmt.Print("What do you want to buy? 1 - espresso, 2 - latte, 3 - cappuccino: ") + fmt.Scanf("%d\n", &choice) + + makeCoffee := func(getMoney, needWater, needMilk, needBeans int) { + if water < needWater { + fmt.Println("Sorry, not enough water!") + } else if milk < needMilk { + fmt.Println("Sorry, not enough milk!") + } else if beans < needBeans { + fmt.Println("Sorry, not enough beans!") + } else if cups < 1 { + fmt.Println("Sorry, not enough cups!") + } else { + fmt.Println("I have enough resources, making you a coffee!") + money += getMoney + water -= needWater + milk -= needMilk + beans -= needBeans + cups-- + } + } + + switch choice { + case 1: + makeCoffee(4, 250, 0, 16) + case 2: + makeCoffee(7, 350, 75, 20) + case 3: + makeCoffee(6, 200, 100, 12) + } + } + + fillMachine := func() { + var next int + fmt.Print("Write how many ml of water do you want to add: ") + fmt.Scanf("%d\n", &next) + water += next + fmt.Print("Write how many ml of milk do you want to add: ") + fmt.Scanf("%d\n", &next) + milk += next + fmt.Print("Write how many grams of coffee beans do you want to add: ") + fmt.Scanf("%d\n", &next) + beans += next + fmt.Print("Write how many disposable cups of coffee do you want to add: ") + fmt.Scanf("%d\n", &next) + cups += next + } + +Loop: + for { + var action string + fmt.Print("\nWrite action (buy, fill, take, remaining, exit): ") + fmt.Scanf("%s\n", &action) + switch action { + case "buy": + sellCoffee() + case "fill": + fillMachine() + case "take": + takeMoney() + case "remaining": + printState() + case "exit": + break Loop + } + } +} diff --git a/src/test/java/projects/go/coffee_machine/stage5/test/CoffeeMachineTestGo5.java b/src/test/java/projects/go/coffee_machine/stage5/test/CoffeeMachineTestGo5.java new file mode 100644 index 00000000..49ab5c1b --- /dev/null +++ b/src/test/java/projects/go/coffee_machine/stage5/test/CoffeeMachineTestGo5.java @@ -0,0 +1,263 @@ +package projects.go.coffee_machine.stage5.test; + +import org.hyperskill.hstest.stage.StageTest; +import org.hyperskill.hstest.testcase.CheckResult; +import org.hyperskill.hstest.testcase.TestCase; + +import java.util.ArrayList; +import java.util.List; + + +class TestClue { + int water; + int milk; + int beans; + int cups; + int money; + String feedback; + TestClue(int w, int m, int b, int c, int mo, String feedback) { + water = w; + milk = m; + beans = b; + cups = c; + money = mo; + this.feedback = feedback; + } +} + +public class CoffeeMachineTestGo5 extends StageTest { + + @Override + public List> generate() { + return List.of( + new TestCase() + .setAttach(new TestClue( + 700 - 400, + 390 - 540, + 80 - 120 , + 7 - 9, + 0 - 550, + "This test is exactly " + + "like in the example - try to run it by yourself")) + .setInput( + "remaining\n" + + "buy\n" + + "2\n" + + "buy\n" + + "2\n" + + "fill\n" + + "1000\n" + + "0\n" + + "0\n" + + "0\n" + + "buy\n" + + "2\n" + + "take\n" + + "remaining\n" + + "exit\n"), + + new TestCase() + .setAttach(new TestClue( + 3000, + 3000, + 3000 , + 3000, + 0, + "This test checks \"fill\" action")) + .setInput( + "remaining\n" + + "fill\n" + + "3000\n" + + "3000\n" + + "3000\n" + + "3000\n" + + "remaining\n" + + "exit\n"), + + new TestCase() + .setAttach(new TestClue( + -250, + 0, + -16 , + -1, + 4, "This test checks \"buy\" " + + "action with the first variant of coffee")) + .setInput( + "remaining\n" + + "buy\n" + + "1\n" + + "remaining\n" + + "exit\n"), + + new TestCase() + .setAttach(new TestClue( + -350, + -75, + -20 , + -1, + 7, "This test checks \"buy\" " + + "action with the second variant of coffee")) + .setInput( + "remaining\n" + + "buy\n" + + "2\n" + + "remaining\n" + + "exit\n"), + + new TestCase() + .setAttach(new TestClue( + -200, + -100, + -12 , + -1, + 6, "This test checks \"buy\" " + + "action with the third variant of coffee")) + .setInput( + "remaining\n" + + "buy\n" + + "3\n" + + "remaining\n" + + "exit\n"), + + new TestCase() + .setAttach(new TestClue( + 0, + 0, + 0 , + 0, + -550, "This test checks \"take\" action")) + .setInput( + "remaining\n" + + "take\n" + + "remaining\n" + + "exit\n"), + + new TestCase() + .setAttach(new TestClue( + 0, + 0, + 0 , + 0, + 0, "This test checks \"back\" " + + "action right after \"buy\" action")) + .setInput( + "remaining\n" + + "buy\n" + + "back\n" + + "remaining\n" + + "exit\n") + ); + } + + @Override + public CheckResult check(String reply, TestClue clue) { + String[] lines = reply.split("\\n"); + + if (lines.length <= 1) { + return CheckResult.wrong("Looks like you didn't print anything!"); + } + + int water_ = clue.water; + int milk_ = clue.milk; + int beans_ = clue.beans; + int cups_ = clue.cups; + int money_ = clue.money; + + List milk = new ArrayList<>(); + List water = new ArrayList<>(); + List beans = new ArrayList<>(); + List cups = new ArrayList<>(); + List money = new ArrayList<>(); + + for (String line : lines) { + line = line.replace("$", "").trim(); + String[] words = line.split("\\s+"); + if (words.length == 0) { + continue; + } + String firstWord = words[0]; + int amount; + try { + amount = Integer.parseInt(firstWord); + } + catch (Exception e) { + continue; + } + if (line.contains("milk")) { + milk.add(amount); + } + else if (line.contains("water")) { + water.add(amount); + } + else if (line.contains("beans")) { + beans.add(amount); + } + else if (line.contains("cups")) { + cups.add(amount); + } + else if (line.contains("money")) { + money.add(amount); + } + } + + if (milk.size() != 2) { + return new CheckResult(false, + "There should be two lines with \"milk\", " + + "found: " + milk.size()); + } + + if (water.size() != 2) { + return new CheckResult(false, + "There should be two lines with \"water\", " + + "found: " + water.size()); + } + + if (beans.size() != 2) { + return new CheckResult(false, + "There should be two lines with \"beans\", " + + "found: " + beans.size()); + } + + if (cups.size() != 2) { + return new CheckResult(false, + "There should be two lines with \"cups\", " + + "found: " + cups.size()); + } + + if (money.size() != 2) { + return new CheckResult(false, + "There should be two lines with \"money\", " + + "found: " + money.size()); + } + + int milk0 = milk.get(0); + int milk1 = milk.get(milk.size() - 1); + + int water0 = water.get(0); + int water1 = water.get(water.size() - 1); + + int beans0 = beans.get(0); + int beans1 = beans.get(beans.size() - 1); + + int cups0 = cups.get(0); + int cups1 = cups.get(cups.size() - 1); + + int money0 = money.get(0); + int money1 = money.get(money.size() - 1); + + int diffWater = water1 - water0; + int diffMilk = milk1 - milk0; + int diffBeans = beans1 - beans0; + int diffCups = cups1 - cups0; + int diffMoney = money1 - money0; + + boolean isCorrect = + diffWater == water_ && + diffMilk == milk_ && + diffBeans == beans_ && + diffCups == cups_ && + diffMoney == money_; + + return new CheckResult(isCorrect, clue.feedback); + } +} diff --git a/src/test/java/projects/javascript/coffee_machine/stage1/main.js b/src/test/java/projects/javascript/coffee_machine/stage1/main.js new file mode 100644 index 00000000..ce5cbbc0 --- /dev/null +++ b/src/test/java/projects/javascript/coffee_machine/stage1/main.js @@ -0,0 +1,8 @@ + +console.log(`Starting to make a coffee +Grinding coffee beans +Boiling water +Mixing boiled water with crushed coffee beans +Pouring coffee into the cup +Pouring some milk into the cup +Coffee is ready!`) diff --git a/src/test/java/projects/javascript/coffee_machine/stage1/test/CoffeeMachineTestJs1.java b/src/test/java/projects/javascript/coffee_machine/stage1/test/CoffeeMachineTestJs1.java new file mode 100644 index 00000000..cd93f323 --- /dev/null +++ b/src/test/java/projects/javascript/coffee_machine/stage1/test/CoffeeMachineTestJs1.java @@ -0,0 +1,34 @@ +package projects.javascript.coffee_machine.stage1.test; + +import org.hyperskill.hstest.stage.StageTest; +import org.hyperskill.hstest.testcase.CheckResult; +import org.hyperskill.hstest.testcase.TestCase; + +import java.util.List; + + +public class CoffeeMachineTestJs1 extends StageTest { + + @Override + public List> generate() { + return List.of( + new TestCase() + .setInput("") + .setAttach("Starting to make a coffee\n" + + "Grinding coffee beans\n" + + "Boiling water\n" + + "Mixing boiled water with crushed coffee beans\n" + + "Pouring coffee into the cup\n" + + "Pouring some milk into the cup\n" + + "Coffee is ready!") + ); + } + + @Override + public CheckResult check(String reply, String clue) { + boolean isCorrect = reply.trim().equals(clue.trim()); + return new CheckResult(isCorrect, + "You should make coffee exactly " + + "like in the example"); + } +} diff --git a/src/test/java/projects/javascript/coffee_machine/stage1_ce/main.js b/src/test/java/projects/javascript/coffee_machine/stage1_ce/main.js new file mode 100644 index 00000000..ca1f4ed5 --- /dev/null +++ b/src/test/java/projects/javascript/coffee_machine/stage1_ce/main.js @@ -0,0 +1,8 @@ + +console.log(`Starting to make a coffee +Grinding coffee beans +Boiling water +Mixing boiled water with crushed coffee beans +Pouring coffee into the cup +Pouring some milk into the cup +Coffee is ready!` diff --git a/src/test/java/projects/javascript/coffee_machine/stage1_ce/test/CoffeeMachineTestJs1Ce.java b/src/test/java/projects/javascript/coffee_machine/stage1_ce/test/CoffeeMachineTestJs1Ce.java new file mode 100644 index 00000000..af3a6c5c --- /dev/null +++ b/src/test/java/projects/javascript/coffee_machine/stage1_ce/test/CoffeeMachineTestJs1Ce.java @@ -0,0 +1,42 @@ +package projects.javascript.coffee_machine.stage1_ce.test; + +import org.hyperskill.hstest.testcase.CheckResult; +import org.hyperskill.hstest.testcase.TestCase; +import outcomes.base.ContainsMessage; +import outcomes.base.UserErrorTest; + +import java.util.List; + + +public class CoffeeMachineTestJs1Ce extends UserErrorTest { + + @ContainsMessage + String msg = "main.js:2\n" + + "console.log(`Starting to make a coffee"; + + @ContainsMessage + String msg2 = "SyntaxError: missing ) after argument list"; + + @Override + public List> generate() { + return List.of( + new TestCase() + .setInput("") + .setAttach("Starting to make a coffee\n" + + "Grinding coffee beans\n" + + "Boiling water\n" + + "Mixing boiled water with crushed coffee beans\n" + + "Pouring coffee into the cup\n" + + "Pouring some milk into the cup\n" + + "Coffee is ready!") + ); + } + + @Override + public CheckResult check(String reply, String clue) { + boolean isCorrect = reply.trim().equals(clue.trim()); + return new CheckResult(isCorrect, + "You should make coffee exactly " + + "like in the example"); + } +} diff --git a/src/test/java/projects/javascript/coffee_machine/stage1_ex/main.js b/src/test/java/projects/javascript/coffee_machine/stage1_ex/main.js new file mode 100644 index 00000000..27776535 --- /dev/null +++ b/src/test/java/projects/javascript/coffee_machine/stage1_ex/main.js @@ -0,0 +1,2 @@ + +console.log(0 .valueOf1()) diff --git a/src/test/java/projects/javascript/coffee_machine/stage1_ex/test/CoffeeMachineTestJs1Ex.java b/src/test/java/projects/javascript/coffee_machine/stage1_ex/test/CoffeeMachineTestJs1Ex.java new file mode 100644 index 00000000..3c2cb8c2 --- /dev/null +++ b/src/test/java/projects/javascript/coffee_machine/stage1_ex/test/CoffeeMachineTestJs1Ex.java @@ -0,0 +1,45 @@ +package projects.javascript.coffee_machine.stage1_ex.test; + +import org.hyperskill.hstest.testcase.CheckResult; +import org.hyperskill.hstest.testcase.TestCase; +import outcomes.base.ContainsMessage; +import outcomes.base.UserErrorTest; + +import java.util.List; + + +public class CoffeeMachineTestJs1Ex extends UserErrorTest { + + @ContainsMessage + String msg = "Exception in test #1"; + + @ContainsMessage + String msg2 = "main.js:2\n" + + "console.log(0 .valueOf1())\n" + + " ^\n" + + "\n" + + "TypeError: 0.valueOf1 is not a function"; + + @Override + public List> generate() { + return List.of( + new TestCase() + .setInput("") + .setAttach("Starting to make a coffee\n" + + "Grinding coffee beans\n" + + "Boiling water\n" + + "Mixing boiled water with crushed coffee beans\n" + + "Pouring coffee into the cup\n" + + "Pouring some milk into the cup\n" + + "Coffee is ready!") + ); + } + + @Override + public CheckResult check(String reply, String clue) { + boolean isCorrect = reply.trim().equals(clue.trim()); + return new CheckResult(isCorrect, + "You should make coffee exactly " + + "like in the example"); + } +} diff --git a/src/test/java/projects/javascript/coffee_machine/stage1_wa/main.js b/src/test/java/projects/javascript/coffee_machine/stage1_wa/main.js new file mode 100644 index 00000000..d9de5338 --- /dev/null +++ b/src/test/java/projects/javascript/coffee_machine/stage1_wa/main.js @@ -0,0 +1 @@ +console.log('12123123') diff --git a/src/test/java/projects/javascript/coffee_machine/stage1_wa/test/CoffeeMachineTestJs1Wa.java b/src/test/java/projects/javascript/coffee_machine/stage1_wa/test/CoffeeMachineTestJs1Wa.java new file mode 100644 index 00000000..5d1aa041 --- /dev/null +++ b/src/test/java/projects/javascript/coffee_machine/stage1_wa/test/CoffeeMachineTestJs1Wa.java @@ -0,0 +1,46 @@ +package projects.javascript.coffee_machine.stage1_wa.test; + +import org.hyperskill.hstest.testcase.CheckResult; +import org.hyperskill.hstest.testcase.TestCase; +import outcomes.base.ContainsMessage; +import outcomes.base.UserErrorTest; + +import java.util.List; + + +public class CoffeeMachineTestJs1Wa extends UserErrorTest { + + @ContainsMessage + String msg = "Wrong answer in test #1\n" + + "\n" + + "You should make coffee exactly like in the example\n" + + "\n" + + "Please find below the output of your program during this failed test.\n" + + "\n" + + "---\n" + + "\n" + + "12123123"; + + @Override + public List> generate() { + return List.of( + new TestCase() + .setInput("") + .setAttach("Starting to make a coffee\n" + + "Grinding coffee beans\n" + + "Boiling water\n" + + "Mixing boiled water with crushed coffee beans\n" + + "Pouring coffee into the cup\n" + + "Pouring some milk into the cup\n" + + "Coffee is ready!") + ); + } + + @Override + public CheckResult check(String reply, String clue) { + boolean isCorrect = reply.trim().equals(clue.trim()); + return new CheckResult(isCorrect, + "You should make coffee exactly " + + "like in the example"); + } +} diff --git a/src/test/java/projects/javascript/coffee_machine/stage2/main.js b/src/test/java/projects/javascript/coffee_machine/stage2/main.js new file mode 100644 index 00000000..a7507c99 --- /dev/null +++ b/src/test/java/projects/javascript/coffee_machine/stage2/main.js @@ -0,0 +1,10 @@ + + + +const input = require('sync-input') + +cups = Number(input("Write how many cups of coffee you will need: ")) +console.log('For ' + cups + " cups of coffee you will need:") +console.log(cups*200 + " ml of water") +console.log(cups*50 + " ml of milk") +console.log(cups*15 + " g of coffee beans") diff --git a/src/test/java/projects/javascript/coffee_machine/stage2/node_modules/sync-input/index.js b/src/test/java/projects/javascript/coffee_machine/stage2/node_modules/sync-input/index.js new file mode 100644 index 00000000..89b3ba63 --- /dev/null +++ b/src/test/java/projects/javascript/coffee_machine/stage2/node_modules/sync-input/index.js @@ -0,0 +1,35 @@ +const fs = require('fs'); + +const carriageReturn = '\r'.charCodeAt(0); +const newLine = '\n'.charCodeAt(0); + +function input(prompt) { + const fd = process.stdin.fd; + + const buf = Buffer.alloc(1); + let str = '', character, read; + + if (prompt) { + process.stdout.write(prompt); + } + + while (true) { + read = fs.readSync(fd, buf, 0, 1); + character = buf[0]; + + if (character === carriageReturn) { + continue + } + + if (character === newLine) { + fs.closeSync(fd); + break; + } + + str += String.fromCharCode(character); + } + + return str; +} + +module.exports = input; diff --git a/src/test/java/projects/javascript/coffee_machine/stage2/test/CoffeeMachineTestJs2.java b/src/test/java/projects/javascript/coffee_machine/stage2/test/CoffeeMachineTestJs2.java new file mode 100644 index 00000000..8adcdfb8 --- /dev/null +++ b/src/test/java/projects/javascript/coffee_machine/stage2/test/CoffeeMachineTestJs2.java @@ -0,0 +1,101 @@ +package projects.javascript.coffee_machine.stage2.test; + +import org.hyperskill.hstest.stage.StageTest; +import org.hyperskill.hstest.testcase.CheckResult; +import org.hyperskill.hstest.testcase.TestCase; + +import java.util.List; + + +public class CoffeeMachineTestJs2 extends StageTest { + + @Override + public List> generate() { + return List.of( + new TestCase() + .setInput("25") + .setAttach("25"), + + new TestCase() + .setInput("125") + .setAttach("125"), + + new TestCase() + .setInput("1") + .setAttach("1"), + + new TestCase() + .setInput("123") + .setAttach("123") + ); + } + + @Override + public CheckResult check(String reply, String clue) { + String[] lines = reply.split("\\n"); + if (lines.length < 3) { + return new CheckResult(false, + "Output contains less than 3 lines, but should output at least 3 lines"); + } + String[] last3Lines = { + lines[lines.length - 3], + lines[lines.length - 2], + lines[lines.length - 1] + }; + + int cups = Integer.parseInt(clue); + boolean water = false, milk = false, beans = false; + + for(String line : last3Lines) { + line = line.toLowerCase(); + + if(line.contains("milk")) { + milk = line.contains(Integer.toString(cups * 50)); + if (!milk) { + return new CheckResult(false, + "For the input " + clue + " your program output:\n\"" + + line + "\"\nbut the amount of milk should be " + (cups * 50)); + } + + } else if(line.contains("water")) { + water = line.contains(Integer.toString(cups * 200)); + if (!water) { + return new CheckResult(false, + "For the input " + clue + " your program output:\n" + + line + "\nbut the amount of water should be " + (cups * 200)); + } + + } else if(line.contains("beans")) { + beans = line.contains(Integer.toString(cups * 15)); + if (!beans) { + return new CheckResult(false, + "For the input " + clue + " your program output:\n" + + line + "\nbut the amount of beans should be " + (cups * 15)); + } + + + } else { + return new CheckResult(false, + "One of the last 3 lines " + + "doesn't contain \"milk\", \"water\" or \"beans\""); + } + } + + if (!water) { + return new CheckResult(false, + "There is no line with amount of water"); + } + + if (!milk) { + return new CheckResult(false, + "There is no line with amount of milk"); + } + + if (!beans) { + return new CheckResult(false, + "There is no line with amount of coffee beans"); + } + + return CheckResult.correct(); + } +} diff --git a/src/test/java/projects/javascript/coffee_machine/stage3/main.js b/src/test/java/projects/javascript/coffee_machine/stage3/main.js new file mode 100644 index 00000000..d24940b9 --- /dev/null +++ b/src/test/java/projects/javascript/coffee_machine/stage3/main.js @@ -0,0 +1,25 @@ +const input = require('sync-input'); + +var cups, water, milk, beans, needs; + +water = Number(input('Write how many ml of water the coffee machine has: ')) +cups = water / 200 | 0; + +milk = Number(input("Write how many ml of milk the coffee machine has: ")) +if ((milk/50|0) < cups) { + cups = milk / 50 | 0; +} + +beans = Number(input("Write how many grams of coffee beans the coffee machine has: ")); +if ((beans/15|0) < cups) { + cups = beans / 15 | 0; +} + +needs = Number(input("Write how many cups of coffee you will need: ")); +if (needs === cups) { + console.log("Yes, I can make that amount of coffee") +} else if (needs < cups) { + console.log("Yes, I can make that amount of coffee (and even " + (cups-needs) + " more than that)") +} else { + console.log("No, I can make only " + cups + " cup(s) of coffee") +} diff --git a/src/test/java/projects/javascript/coffee_machine/stage3/node_modules/sync-input/index.js b/src/test/java/projects/javascript/coffee_machine/stage3/node_modules/sync-input/index.js new file mode 100644 index 00000000..89b3ba63 --- /dev/null +++ b/src/test/java/projects/javascript/coffee_machine/stage3/node_modules/sync-input/index.js @@ -0,0 +1,35 @@ +const fs = require('fs'); + +const carriageReturn = '\r'.charCodeAt(0); +const newLine = '\n'.charCodeAt(0); + +function input(prompt) { + const fd = process.stdin.fd; + + const buf = Buffer.alloc(1); + let str = '', character, read; + + if (prompt) { + process.stdout.write(prompt); + } + + while (true) { + read = fs.readSync(fd, buf, 0, 1); + character = buf[0]; + + if (character === carriageReturn) { + continue + } + + if (character === newLine) { + fs.closeSync(fd); + break; + } + + str += String.fromCharCode(character); + } + + return str; +} + +module.exports = input; diff --git a/src/test/java/projects/javascript/coffee_machine/stage3/test/CoffeeMachineTestJs3.java b/src/test/java/projects/javascript/coffee_machine/stage3/test/CoffeeMachineTestJs3.java new file mode 100644 index 00000000..fb8a2010 --- /dev/null +++ b/src/test/java/projects/javascript/coffee_machine/stage3/test/CoffeeMachineTestJs3.java @@ -0,0 +1,167 @@ +package projects.javascript.coffee_machine.stage3.test; + +import org.hyperskill.hstest.stage.StageTest; +import org.hyperskill.hstest.testcase.CheckResult; +import org.hyperskill.hstest.testcase.TestCase; + +import java.util.List; + + +class TestClue { + boolean cond; + int num; + boolean showTests; + TestClue(boolean c, int n, boolean showTests) { + cond = c; + num = n; + this.showTests = showTests; + } +} + +public class CoffeeMachineTestJs3 extends StageTest { + + @Override + public List> generate() { + return List.of( + new TestCase() + .setAttach(new TestClue(true, 0, true)) + .setInput("300\n65\n111\n1"), + + new TestCase() + .setAttach(new TestClue(true, 2, true)) + .setInput("600\n153\n100\n1"), + + new TestCase() + .setAttach(new TestClue(true, 2, true)) + .setInput("1400\n150\n100\n1"), + + new TestCase() + .setAttach(new TestClue(true, 2, true)) + .setInput("1400\n1500\n45\n1"), + + new TestCase() + .setAttach(new TestClue(false, 2, true)) + .setInput("599\n250\n200\n10"), + + new TestCase() + .setAttach(new TestClue(true, 867, true)) + .setInput( "345640\n43423\n23234\n1"), + + new TestCase() + .setAttach(new TestClue(false, 1548, true)) + .setInput("345640\n434230\n23234\n19246"), + + new TestCase() + .setAttach(new TestClue(false, 172, true)) + .setInput( "34564\n43423\n23234\n19246"), + + new TestCase() + .setAttach(new TestClue(true, 0, false)) + .setInput("399\n112\n111\n1"), + + new TestCase() + .setAttach(new TestClue(true, 3, false)) + .setInput("2400\n249\n100\n1"), + + new TestCase() + .setAttach(new TestClue(true, 1, false)) + .setInput("1400\n1500\n44\n1"), + + new TestCase() + .setAttach(new TestClue(false, 2, false)) + .setInput("500\n250\n200\n10"), + + new TestCase() + .setAttach(new TestClue(true, 171, false)) + .setInput("34564\n43423\n23234\n1"), + + new TestCase() + .setAttach(new TestClue(true, 1547, false)) + .setInput("345640\n434230\n23234\n1"), + + new TestCase() + .setAttach(new TestClue(false, 868, false)) + .setInput("345640\n43423\n23234\n19246") + + ); + } + + @Override + public CheckResult check(String reply, TestClue clue) { + String[] lines = reply.trim().split(":"); + + if (lines.length <= 1) { + return CheckResult.wrong("Looks like you didn't print anything!"); + } + + String userOutput = lines[lines.length - 1].trim(); + String loweredOutput = userOutput.toLowerCase(); + boolean ans = clue.cond; + int amount = clue.num; + if (ans) { + if (amount > 0) { + boolean isCorrect = + loweredOutput.contains(Integer.toString(amount)) + && loweredOutput.contains("yes"); + + if (isCorrect) { + return CheckResult.correct(); + } else { + + String rightOutput = + "Yes, I can make that amount of coffee" + + " (and even " + amount + " more than that)"; + + if (clue.showTests) { + return new CheckResult(false, + "Your output:\n" + + userOutput + + "\nRight output:\n" + + rightOutput); + } else { + return CheckResult.wrong(""); + } + } + } + + String rightOutput = + "Yes, I can make that amount of coffee"; + + if (loweredOutput.equals(rightOutput.toLowerCase())) { + return CheckResult.correct(); + } else { + if (clue.showTests) { + return new CheckResult(false, + "Your output:\n" + + userOutput + + "\nRight output:\n" + + rightOutput); + } else { + return CheckResult.wrong(""); + } + } + + } else { + boolean cond1 = loweredOutput.contains("no"); + boolean cond2 = loweredOutput.contains(Integer.toString(amount)); + + if (cond1 && cond2) { + return CheckResult.correct(); + } else { + + String rightOutput = "No, I can make only " + + amount +" cup(s) of coffee"; + + if (clue.showTests) { + return new CheckResult(false, + "Your output:\n" + + userOutput + + "\nRight output:\n" + + rightOutput); + } else { + return CheckResult.wrong(""); + } + } + } + } +} diff --git a/src/test/java/projects/javascript/coffee_machine/stage4/main.js b/src/test/java/projects/javascript/coffee_machine/stage4/main.js new file mode 100644 index 00000000..e68cc695 --- /dev/null +++ b/src/test/java/projects/javascript/coffee_machine/stage4/main.js @@ -0,0 +1,70 @@ +const input = require('sync-input'); + +var money = 550; +var water = 400; +var milk = 540; +var beans = 120; +var cups = 9; + +function printState() { + console.log("The coffee machine has:") + console.log( + water + " of water \n" + + milk + " of milk \n" + + beans + " of coffee beans \n" + + cups + " of disposable cups \n" + + money + " of money") +} + +function takeMoney() { + console.log("I gave you $" + money) + money = 0 +} + +function sellCoffee() { + let choice = input("What do you want to buy? 1 - espresso, 2 - latte, 3 - cappuccino: ") + if (choice === '1') { + console.log(123) + money += 4 + water -= 250 + beans -= 16 + cups -= 1 + } + else if (choice === '2') { + money += 7 + water -= 350 + milk -= 75 + beans -= 20 + cups -= 1 + } + else if (choice === '3') { + money += 6 + water -= 200 + milk -= 100 + beans -= 12 + cups -= 1 + } else { + console.log(234) + } +} + +function fillMachine() { + water += Number(input("Write how many ml of water do you want to add: ")) + milk += Number(input("Write how many ml of milk do you want to add: ")) + beans += Number(input("Write how many grams of coffee beans do you want to add: ")) + cups += Number(input("Write how many disposable cups of coffee do you want to add: ")) +} + +printState(); + +action = input("Write action (buy, fill, take): "); + +if (action === 'buy') { + sellCoffee() +} else if (action === 'fill') { + fillMachine() +} else if (action === "take") { + takeMoney() +} + +printState(); diff --git a/src/test/java/projects/javascript/coffee_machine/stage4/node_modules/sync-input/index.js b/src/test/java/projects/javascript/coffee_machine/stage4/node_modules/sync-input/index.js new file mode 100644 index 00000000..89b3ba63 --- /dev/null +++ b/src/test/java/projects/javascript/coffee_machine/stage4/node_modules/sync-input/index.js @@ -0,0 +1,35 @@ +const fs = require('fs'); + +const carriageReturn = '\r'.charCodeAt(0); +const newLine = '\n'.charCodeAt(0); + +function input(prompt) { + const fd = process.stdin.fd; + + const buf = Buffer.alloc(1); + let str = '', character, read; + + if (prompt) { + process.stdout.write(prompt); + } + + while (true) { + read = fs.readSync(fd, buf, 0, 1); + character = buf[0]; + + if (character === carriageReturn) { + continue + } + + if (character === newLine) { + fs.closeSync(fd); + break; + } + + str += String.fromCharCode(character); + } + + return str; +} + +module.exports = input; diff --git a/src/test/java/projects/javascript/coffee_machine/stage4/test/CoffeeMachineTestJs4.java b/src/test/java/projects/javascript/coffee_machine/stage4/test/CoffeeMachineTestJs4.java new file mode 100644 index 00000000..879a56af --- /dev/null +++ b/src/test/java/projects/javascript/coffee_machine/stage4/test/CoffeeMachineTestJs4.java @@ -0,0 +1,346 @@ +package projects.javascript.coffee_machine.stage4.test; + +import org.hyperskill.hstest.stage.StageTest; +import org.hyperskill.hstest.testcase.CheckResult; +import org.hyperskill.hstest.testcase.TestCase; + +import java.util.ArrayList; +import java.util.List; + + +class TestClue { + String string; + TestClue(String s) { + string = s; + } +} + +public class CoffeeMachineTestJs4 extends StageTest { + + @Override + public List> generate() { + return List.of( + new TestCase() + .setAttach(new TestClue("take\n")) + .setInput("take\n"), + + new TestCase() + .setAttach(new TestClue("buy\n1\n")) + .setInput("buy\n1\n"), + + new TestCase() + .setAttach(new TestClue("buy\n2\n")) + .setInput("buy\n2\n"), + + new TestCase() + .setAttach(new TestClue("buy\n3\n")) + .setInput("buy\n3\n"), + + new TestCase() + .setAttach(new TestClue("fill\n2001\n510\n101\n21\n")) + .setInput("fill\n2001\n510\n101\n21\n") + ); + } + + @Override + public CheckResult check(String reply, TestClue clue) { + String[] lines = reply.trim().split("\\n"); + + if (lines.length <= 1) { + return CheckResult.wrong("Looks like you didn't print anything!"); + } + + String[] clueLines = clue.string.trim().split("\\n"); + String action = clueLines[0].trim(); + + List milk = new ArrayList<>(); + List water = new ArrayList<>(); + List beans = new ArrayList<>(); + List cups = new ArrayList<>(); + List money = new ArrayList<>(); + + for (String line : lines) { + String[] words = line.split("\\s+"); + if (words.length == 0) { + continue; + } + String firstWord = words[0].replace("$", ""); + int amount; + try { + amount = Integer.parseInt(firstWord); + } + catch (Exception e) { + continue; + } + if (line.contains("milk")) { + milk.add(amount); + } + else if (line.contains("water")) { + water.add(amount); + } + else if (line.contains("beans")) { + beans.add(amount); + } + else if (line.contains("cups")) { + cups.add(amount); + } + else if (line.contains("money")) { + money.add(amount); + } + } + + if (milk.size() != 2) { + return new CheckResult(false, + "There should be two lines with \"milk\", " + + "found: " + milk.size()); + } + + if (water.size() != 2) { + return new CheckResult(false, + "There should be two lines with \"water\", " + + "found: " + water.size()); + } + + if (beans.size() != 2) { + return new CheckResult(false, + "There should be two lines with \"beans\", " + + "found: " + beans.size()); + } + + if (cups.size() != 2) { + return new CheckResult(false, + "There should be two lines with \"cups\", " + + "found: " + cups.size()); + } + + if (money.size() != 2) { + return new CheckResult(false, + "There should be two lines with \"money\", " + + "found: " + money.size()); + } + + + int milk0 = milk.get(0); + int milk1 = milk.get(milk.size() - 1); + + int water0 = water.get(0); + int water1 = water.get(water.size() - 1); + + int beans0 = beans.get(0); + int beans1 = beans.get(beans.size() - 1); + + int cups0 = cups.get(0); + int cups1 = cups.get(cups.size() - 1); + + int money0 = money.get(0); + int money1 = money.get(money.size() - 1); + + if (water0 != 400 || milk0 != 540 || beans0 != 120 + || cups0 != 9 || money0 != 550) { + return new CheckResult(false, + "Initial setup is wrong: " + + "coffee machine should fe filled like " + + "stated in the description"); + } + + int diffWater = water1 - water0; + int diffMilk = milk1 - milk0; + int diffBeans = beans1 - beans0; + int diffCups = cups1 - cups0; + int diffMoney = money1 - money0; + + if (action.equals("take")) { + + if (diffMilk != 0) { + return new CheckResult(false, + "After \"take\" action milk " + + "amount shouldn't be changed"); + } + + if (diffWater != 0) { + return new CheckResult(false, + "After \"take\" action water " + + "amount shouldn't be changed"); + } + + if (diffBeans != 0) { + return new CheckResult(false, + "After \"take\" action beans " + + "amount shouldn't be changed"); + } + + if (diffCups != 0) { + return new CheckResult(false, + "After \"take\" action cups " + + "amount shouldn't be changed"); + } + + if (money1 != 0) { + return new CheckResult(false, + "After \"take\" action money " + + "amount should be zero"); + } + + return CheckResult.correct(); + } + + else if (action.equals("buy")) { + + String option = clueLines[1].trim(); + + if (option.equals("1")) { + + if (diffWater != -250) { + return new CheckResult(false, + "After buying the first option " + + "water amount should be lowered by 250"); + } + + if (diffMilk != 0) { + return new CheckResult(false, + "After buying the first option " + + "milk amount should not be changed"); + } + + if (diffBeans != -16) { + return new CheckResult(false, + "After buying the first option " + + "beans amount should be lowered by 16"); + } + + if (diffCups != -1) { + return new CheckResult(false, + "After buying the first option " + + "cups amount should be lowered by 1"); + } + + if (diffMoney != 4) { + return new CheckResult(false, + "After buying the first option " + + "money amount should be increased by 4"); + } + + return CheckResult.correct(); + + } + + else if (option.equals("2")) { + + if (diffWater != -350) { + return new CheckResult(false, + "After buying the second option " + + "water amount should be lowered by 350"); + } + + if (diffMilk != -75) { + return new CheckResult(false, + "After buying the second option " + + "milk amount should be lowered by 75"); + } + + if (diffBeans != -20) { + return new CheckResult(false, + "After buying the second option " + + "beans amount should be lowered by 20"); + } + + if (diffCups != -1) { + return new CheckResult(false, + "After buying the second option " + + "cups amount should be lowered by 1"); + } + + if (diffMoney != 7) { + return new CheckResult(false, + "After buying the second option " + + "money amount should be increased by 7"); + } + + return CheckResult.correct(); + } + + else if (option.equals("3")) { + + if (diffWater != -200) { + return new CheckResult(false, + "After buying the third option " + + "water amount should be lowered by 350"); + } + + if (diffMilk != -100) { + return new CheckResult(false, + "After buying the third option " + + "milk amount should be lowered by 75"); + } + + if (diffBeans != -12) { + return new CheckResult(false, + "After buying the third option " + + "beans amount should be lowered by 20"); + } + + if (diffCups != -1) { + return new CheckResult(false, + "After buying the third option " + + "cups amount should be lowered by 1"); + } + + if (diffMoney != 6) { + return new CheckResult(false, + "After buying the third option " + + "money amount should be increased by 7"); + } + + return CheckResult.correct(); + } + } + + else if (action.equals("fill")) { + + int water_ = Integer.parseInt(clueLines[1]); + int milk_ = Integer.parseInt(clueLines[2]); + int beans_ = Integer.parseInt(clueLines[3]); + int cups_ = Integer.parseInt(clueLines[4]); + + if (diffMoney != 0) { + return new CheckResult(false, + "After \"fill\" action " + + "money amount should not be changed"); + } + + if (diffWater != water_) { + return new CheckResult(false, + "After \"fill\" action " + + "water amount expected to be increased by " + water_ + + " but was increased by " + diffWater); + } + + if (diffMilk != milk_) { + return new CheckResult(false, + "After \"fill\" action " + + "milk amount expected to be increased by " + milk_ + + " but was increased by " + diffMilk); + } + + if (diffBeans != beans_) { + return new CheckResult(false, + "After \"fill\" action " + + "beans amount expected to be increased by " + beans_ + + " but was increased by " + diffBeans); + } + + if (diffCups != cups_) { + return new CheckResult(false, + "After \"fill\" action " + + "cups amount expected to be increased by " + cups_ + + " but was increased by " + diffCups); + } + + + return CheckResult.correct(); + + } + + throw new RuntimeException("Can't check the answer"); + } +} diff --git a/src/test/java/projects/javascript/coffee_machine/stage5/main.js b/src/test/java/projects/javascript/coffee_machine/stage5/main.js new file mode 100644 index 00000000..dc293e26 --- /dev/null +++ b/src/test/java/projects/javascript/coffee_machine/stage5/main.js @@ -0,0 +1,77 @@ +const input = require('sync-input'); + +var money = 550; +var water = 400; +var milk = 540; +var beans = 120; +var cups = 9; + +function printState() { + console.log("The coffee machine has:") + console.log( + water + " of water \n" + + milk + " of milk \n" + + beans + " of coffee beans \n" + + cups + " of disposable cups \n" + + money + " of money") +} + +function takeMoney() { + console.log("I gave you $" + money) + money = 0 +} + +function sellCoffee() { + let choice = input("What do you want to buy? 1 - espresso, 2 - latte, 3 - cappuccino: "); + + function makeCoffee(getMoney, needWater, needMilk, needBeans) { + if (water < needWater) { + console.log("Sorry, not enough water!") + } else if (milk < needMilk) { + console.log("Sorry, not enough milk!") + } else if (beans < needBeans) { + console.log("Sorry, not enough beans!") + } else if (cups < 1) { + console.log("Sorry, not enough cups!") + } else { + console.log("I have enough resources, making you a coffee!") + money += getMoney + water -= needWater + milk -= needMilk + beans -= needBeans + cups-- + } + } + + if (choice === '1') { + makeCoffee(4, 250, 0, 16) + } else if (choice === '2') { + makeCoffee(7, 350, 75, 20) + } else if (choice === '3') { + makeCoffee(6, 200, 100, 12) + } +} + +function fillMachine() { + water += Number(input("Write how many ml of water do you want to add: ")) + milk += Number(input("Write how many ml of milk do you want to add: ")) + beans += Number(input("Write how many grams of coffee beans do you want to add: ")) + cups += Number(input("Write how many disposable cups of coffee do you want to add: ")) +} + + +while (true) { + action = input("Write action (buy, fill, take, remaining, exit): ") + + if (action === 'buy') { + sellCoffee() + } else if (action === 'fill') { + fillMachine() + } else if (action === "take") { + takeMoney() + } else if (action === "remaining") { + printState(); + } else if (action === 'exit') { + break + } +} diff --git a/src/test/java/projects/javascript/coffee_machine/stage5/node_modules/sync-input/index.js b/src/test/java/projects/javascript/coffee_machine/stage5/node_modules/sync-input/index.js new file mode 100644 index 00000000..89b3ba63 --- /dev/null +++ b/src/test/java/projects/javascript/coffee_machine/stage5/node_modules/sync-input/index.js @@ -0,0 +1,35 @@ +const fs = require('fs'); + +const carriageReturn = '\r'.charCodeAt(0); +const newLine = '\n'.charCodeAt(0); + +function input(prompt) { + const fd = process.stdin.fd; + + const buf = Buffer.alloc(1); + let str = '', character, read; + + if (prompt) { + process.stdout.write(prompt); + } + + while (true) { + read = fs.readSync(fd, buf, 0, 1); + character = buf[0]; + + if (character === carriageReturn) { + continue + } + + if (character === newLine) { + fs.closeSync(fd); + break; + } + + str += String.fromCharCode(character); + } + + return str; +} + +module.exports = input; diff --git a/src/test/java/projects/javascript/coffee_machine/stage5/test/CoffeeMachineTestJs5.java b/src/test/java/projects/javascript/coffee_machine/stage5/test/CoffeeMachineTestJs5.java new file mode 100644 index 00000000..f4cbbfd5 --- /dev/null +++ b/src/test/java/projects/javascript/coffee_machine/stage5/test/CoffeeMachineTestJs5.java @@ -0,0 +1,263 @@ +package projects.javascript.coffee_machine.stage5.test; + +import org.hyperskill.hstest.stage.StageTest; +import org.hyperskill.hstest.testcase.CheckResult; +import org.hyperskill.hstest.testcase.TestCase; + +import java.util.ArrayList; +import java.util.List; + + +class TestClue { + int water; + int milk; + int beans; + int cups; + int money; + String feedback; + TestClue(int w, int m, int b, int c, int mo, String feedback) { + water = w; + milk = m; + beans = b; + cups = c; + money = mo; + this.feedback = feedback; + } +} + +public class CoffeeMachineTestJs5 extends StageTest { + + @Override + public List> generate() { + return List.of( + new TestCase() + .setAttach(new TestClue( + 700 - 400, + 390 - 540, + 80 - 120 , + 7 - 9, + 0 - 550, + "This test is exactly " + + "like in the example - try to run it by yourself")) + .setInput( + "remaining\n" + + "buy\n" + + "2\n" + + "buy\n" + + "2\n" + + "fill\n" + + "1000\n" + + "0\n" + + "0\n" + + "0\n" + + "buy\n" + + "2\n" + + "take\n" + + "remaining\n" + + "exit\n"), + + new TestCase() + .setAttach(new TestClue( + 3000, + 3000, + 3000 , + 3000, + 0, + "This test checks \"fill\" action")) + .setInput( + "remaining\n" + + "fill\n" + + "3000\n" + + "3000\n" + + "3000\n" + + "3000\n" + + "remaining\n" + + "exit\n"), + + new TestCase() + .setAttach(new TestClue( + -250, + 0, + -16 , + -1, + 4, "This test checks \"buy\" " + + "action with the first variant of coffee")) + .setInput( + "remaining\n" + + "buy\n" + + "1\n" + + "remaining\n" + + "exit\n"), + + new TestCase() + .setAttach(new TestClue( + -350, + -75, + -20 , + -1, + 7, "This test checks \"buy\" " + + "action with the second variant of coffee")) + .setInput( + "remaining\n" + + "buy\n" + + "2\n" + + "remaining\n" + + "exit\n"), + + new TestCase() + .setAttach(new TestClue( + -200, + -100, + -12 , + -1, + 6, "This test checks \"buy\" " + + "action with the third variant of coffee")) + .setInput( + "remaining\n" + + "buy\n" + + "3\n" + + "remaining\n" + + "exit\n"), + + new TestCase() + .setAttach(new TestClue( + 0, + 0, + 0 , + 0, + -550, "This test checks \"take\" action")) + .setInput( + "remaining\n" + + "take\n" + + "remaining\n" + + "exit\n"), + + new TestCase() + .setAttach(new TestClue( + 0, + 0, + 0 , + 0, + 0, "This test checks \"back\" " + + "action right after \"buy\" action")) + .setInput( + "remaining\n" + + "buy\n" + + "back\n" + + "remaining\n" + + "exit\n") + ); + } + + @Override + public CheckResult check(String reply, TestClue clue) { + String[] lines = reply.split("\\n"); + + if (lines.length <= 1) { + return CheckResult.wrong("Looks like you didn't print anything!"); + } + + int water_ = clue.water; + int milk_ = clue.milk; + int beans_ = clue.beans; + int cups_ = clue.cups; + int money_ = clue.money; + + List milk = new ArrayList<>(); + List water = new ArrayList<>(); + List beans = new ArrayList<>(); + List cups = new ArrayList<>(); + List money = new ArrayList<>(); + + for (String line : lines) { + line = line.replace("$", "").trim(); + String[] words = line.split("\\s+"); + if (words.length == 0) { + continue; + } + String firstWord = words[0]; + int amount; + try { + amount = Integer.parseInt(firstWord); + } + catch (Exception e) { + continue; + } + if (line.contains("milk")) { + milk.add(amount); + } + else if (line.contains("water")) { + water.add(amount); + } + else if (line.contains("beans")) { + beans.add(amount); + } + else if (line.contains("cups")) { + cups.add(amount); + } + else if (line.contains("money")) { + money.add(amount); + } + } + + if (milk.size() != 2) { + return new CheckResult(false, + "There should be two lines with \"milk\", " + + "found: " + milk.size()); + } + + if (water.size() != 2) { + return new CheckResult(false, + "There should be two lines with \"water\", " + + "found: " + water.size()); + } + + if (beans.size() != 2) { + return new CheckResult(false, + "There should be two lines with \"beans\", " + + "found: " + beans.size()); + } + + if (cups.size() != 2) { + return new CheckResult(false, + "There should be two lines with \"cups\", " + + "found: " + cups.size()); + } + + if (money.size() != 2) { + return new CheckResult(false, + "There should be two lines with \"money\", " + + "found: " + money.size()); + } + + int milk0 = milk.get(0); + int milk1 = milk.get(milk.size() - 1); + + int water0 = water.get(0); + int water1 = water.get(water.size() - 1); + + int beans0 = beans.get(0); + int beans1 = beans.get(beans.size() - 1); + + int cups0 = cups.get(0); + int cups1 = cups.get(cups.size() - 1); + + int money0 = money.get(0); + int money1 = money.get(money.size() - 1); + + int diffWater = water1 - water0; + int diffMilk = milk1 - milk0; + int diffBeans = beans1 - beans0; + int diffCups = cups1 - cups0; + int diffMoney = money1 - money0; + + boolean isCorrect = + diffWater == water_ && + diffMilk == milk_ && + diffBeans == beans_ && + diffCups == cups_ && + diffMoney == money_; + + return new CheckResult(isCorrect, clue.feedback); + } +} diff --git a/src/test/java/projects/python/coffee_machine/stage1/machine/coffee_machine.py b/src/test/java/projects/python/coffee_machine/stage1/machine/coffee_machine.py new file mode 100644 index 00000000..9217884a --- /dev/null +++ b/src/test/java/projects/python/coffee_machine/stage1/machine/coffee_machine.py @@ -0,0 +1,8 @@ +# Write your code here +print("""Starting to make a coffee +Grinding coffee beans +Boiling water +Mixing boiled water with crushed coffee beans +Pouring coffee into the cup +Pouring some milk into the cup +Coffee is ready!""") diff --git a/src/test/java/projects/python/coffee_machine/stage1/test/CoffeeMachineTestPy1.java b/src/test/java/projects/python/coffee_machine/stage1/test/CoffeeMachineTestPy1.java new file mode 100644 index 00000000..9f394324 --- /dev/null +++ b/src/test/java/projects/python/coffee_machine/stage1/test/CoffeeMachineTestPy1.java @@ -0,0 +1,34 @@ +package projects.python.coffee_machine.stage1.test; + +import org.hyperskill.hstest.stage.StageTest; +import org.hyperskill.hstest.testcase.CheckResult; +import org.hyperskill.hstest.testcase.TestCase; + +import java.util.List; + + +public class CoffeeMachineTestPy1 extends StageTest { + + @Override + public List> generate() { + return List.of( + new TestCase() + .setInput("") + .setAttach("Starting to make a coffee\n" + + "Grinding coffee beans\n" + + "Boiling water\n" + + "Mixing boiled water with crushed coffee beans\n" + + "Pouring coffee into the cup\n" + + "Pouring some milk into the cup\n" + + "Coffee is ready!") + ); + } + + @Override + public CheckResult check(String reply, String clue) { + boolean isCorrect = reply.trim().equals(clue.trim()); + return new CheckResult(isCorrect, + "You should make coffee exactly " + + "like in the example"); + } +} diff --git a/src/test/java/projects/python/coffee_machine/stage1_ce/machine/coffee_machine.py b/src/test/java/projects/python/coffee_machine/stage1_ce/machine/coffee_machine.py new file mode 100644 index 00000000..dd6b737e --- /dev/null +++ b/src/test/java/projects/python/coffee_machine/stage1_ce/machine/coffee_machine.py @@ -0,0 +1,8 @@ +# Write your code here +print("""Starting to make a coffee +Grinding coffee beans +Boiling water +Mixing boiled water with crushed coffee beans +Pouring coffee into the cup +Pouring some milk into the cup +Coffee is ready!""" diff --git a/src/test/java/projects/python/coffee_machine/stage1_ce/test/CoffeeMachineTestPy1Ce.java b/src/test/java/projects/python/coffee_machine/stage1_ce/test/CoffeeMachineTestPy1Ce.java new file mode 100644 index 00000000..cf3c3214 --- /dev/null +++ b/src/test/java/projects/python/coffee_machine/stage1_ce/test/CoffeeMachineTestPy1Ce.java @@ -0,0 +1,50 @@ +package projects.python.coffee_machine.stage1_ce.test; + +import org.hyperskill.hstest.testcase.CheckResult; +import org.hyperskill.hstest.testcase.TestCase; +import outcomes.base.ContainsMessage; +import outcomes.base.NotContainMessage; +import outcomes.base.UserErrorTest; + +import java.util.List; + + +public class CoffeeMachineTestPy1Ce extends UserErrorTest { + + @ContainsMessage + String msg = "Exception in test #1\n" + + "\n" + + "File \""; + + @ContainsMessage + String msg2 = "\", line"; + + @ContainsMessage + String m3 = "SyntaxError:"; + + @NotContainMessage + String m4 = "stderr:"; + + @Override + public List> generate() { + return List.of( + new TestCase() + .setInput("") + .setAttach("Starting to make a coffee\n" + + "Grinding coffee beans\n" + + "Boiling water\n" + + "Mixing boiled water with crushed coffee beans\n" + + "Pouring coffee into the cup\n" + + "Pouring some milk into the cup\n" + + "Coffee is ready!") + ); + } + + @Override + public CheckResult check(String reply, String clue) { + boolean isCorrect = reply.trim().equals(clue.trim()); + return new CheckResult(isCorrect, + "You should make coffee exactly " + + "like in the example"); + } +} diff --git a/src/test/java/projects/python/coffee_machine/stage1_ex/machine/coffee_machine.py b/src/test/java/projects/python/coffee_machine/stage1_ex/machine/coffee_machine.py new file mode 100644 index 00000000..3d240de9 --- /dev/null +++ b/src/test/java/projects/python/coffee_machine/stage1_ex/machine/coffee_machine.py @@ -0,0 +1,2 @@ +# Write your code here +print(0/0) diff --git a/src/test/java/projects/python/coffee_machine/stage1_ex/test/CoffeeMachineTestPy1Ex.java b/src/test/java/projects/python/coffee_machine/stage1_ex/test/CoffeeMachineTestPy1Ex.java new file mode 100644 index 00000000..e6a170a4 --- /dev/null +++ b/src/test/java/projects/python/coffee_machine/stage1_ex/test/CoffeeMachineTestPy1Ex.java @@ -0,0 +1,47 @@ +package projects.python.coffee_machine.stage1_ex.test; + +import org.hyperskill.hstest.testcase.CheckResult; +import org.hyperskill.hstest.testcase.TestCase; +import outcomes.base.ContainsMessage; +import outcomes.base.NotContainMessage; +import outcomes.base.UserErrorTest; + +import java.util.List; + + +public class CoffeeMachineTestPy1Ex extends UserErrorTest { + + @ContainsMessage + String msg = "Exception in test #1\n" + + "\n" + + "Traceback (most recent call last):"; + + @ContainsMessage + String msg2 = "ZeroDivisionError: division by zero"; + + @NotContainMessage + String m3 = "stderr:"; + + @Override + public List> generate() { + return List.of( + new TestCase() + .setInput("") + .setAttach("Starting to make a coffee\n" + + "Grinding coffee beans\n" + + "Boiling water\n" + + "Mixing boiled water with crushed coffee beans\n" + + "Pouring coffee into the cup\n" + + "Pouring some milk into the cup\n" + + "Coffee is ready!") + ); + } + + @Override + public CheckResult check(String reply, String clue) { + boolean isCorrect = reply.trim().equals(clue.trim()); + return new CheckResult(isCorrect, + "You should make coffee exactly " + + "like in the example"); + } +} diff --git a/src/test/java/projects/python/coffee_machine/stage1_wa/machine/coffee_machine.py b/src/test/java/projects/python/coffee_machine/stage1_wa/machine/coffee_machine.py new file mode 100644 index 00000000..690f7c0f --- /dev/null +++ b/src/test/java/projects/python/coffee_machine/stage1_wa/machine/coffee_machine.py @@ -0,0 +1,2 @@ +# Write your code here +print("""12123123""") diff --git a/src/test/java/projects/python/coffee_machine/stage1_wa/test/CoffeeMachineTestPy1Wa.java b/src/test/java/projects/python/coffee_machine/stage1_wa/test/CoffeeMachineTestPy1Wa.java new file mode 100644 index 00000000..04b19e59 --- /dev/null +++ b/src/test/java/projects/python/coffee_machine/stage1_wa/test/CoffeeMachineTestPy1Wa.java @@ -0,0 +1,46 @@ +package projects.python.coffee_machine.stage1_wa.test; + +import org.hyperskill.hstest.testcase.CheckResult; +import org.hyperskill.hstest.testcase.TestCase; +import outcomes.base.ContainsMessage; +import outcomes.base.UserErrorTest; + +import java.util.List; + + +public class CoffeeMachineTestPy1Wa extends UserErrorTest { + + @ContainsMessage + String msg = "Wrong answer in test #1\n" + + "\n" + + "You should make coffee exactly like in the example\n" + + "\n" + + "Please find below the output of your program during this failed test.\n" + + "\n" + + "---\n" + + "\n" + + "12123123"; + + @Override + public List> generate() { + return List.of( + new TestCase() + .setInput("") + .setAttach("Starting to make a coffee\n" + + "Grinding coffee beans\n" + + "Boiling water\n" + + "Mixing boiled water with crushed coffee beans\n" + + "Pouring coffee into the cup\n" + + "Pouring some milk into the cup\n" + + "Coffee is ready!") + ); + } + + @Override + public CheckResult check(String reply, String clue) { + boolean isCorrect = reply.trim().equals(clue.trim()); + return new CheckResult(isCorrect, + "You should make coffee exactly " + + "like in the example"); + } +} diff --git a/src/test/java/projects/python/coffee_machine/stage2/machine/coffee_machine.py b/src/test/java/projects/python/coffee_machine/stage2/machine/coffee_machine.py new file mode 100644 index 00000000..f3ff8f2c --- /dev/null +++ b/src/test/java/projects/python/coffee_machine/stage2/machine/coffee_machine.py @@ -0,0 +1,18 @@ +# Write your code here +# print("""Starting to make a coffee +# Grinding coffee beans +# Boiling water +# Mixing boiled water with crushed coffee beans +# Pouring coffee into the cup +# Pouring some milk into the cup +# Coffee is ready!""") +print("Write how many cups of coffee you will need:") +cups = int(input()) + +water = cups * 200 +milk = cups * 50 +beans = cups * 15 +print(f"for {cups} cups of coffee you will need:") +print(f"{water} ml of water") +print(f"{milk} ml of milk") +print(f"{beans} g of coffee beans") diff --git a/src/test/java/projects/python/coffee_machine/stage2/test/CoffeeMachineTestPy2.java b/src/test/java/projects/python/coffee_machine/stage2/test/CoffeeMachineTestPy2.java new file mode 100644 index 00000000..0730d113 --- /dev/null +++ b/src/test/java/projects/python/coffee_machine/stage2/test/CoffeeMachineTestPy2.java @@ -0,0 +1,101 @@ +package projects.python.coffee_machine.stage2.test; + +import org.hyperskill.hstest.stage.StageTest; +import org.hyperskill.hstest.testcase.CheckResult; +import org.hyperskill.hstest.testcase.TestCase; + +import java.util.List; + + +public class CoffeeMachineTestPy2 extends StageTest { + + @Override + public List> generate() { + return List.of( + new TestCase() + .setInput("25") + .setAttach("25"), + + new TestCase() + .setInput("125") + .setAttach("125"), + + new TestCase() + .setInput("1") + .setAttach("1"), + + new TestCase() + .setInput("123") + .setAttach("123") + ); + } + + @Override + public CheckResult check(String reply, String clue) { + String[] lines = reply.split("\\n"); + if (lines.length < 3) { + return new CheckResult(false, + "Output contains less than 3 lines, but should output at least 3 lines"); + } + String[] last3Lines = { + lines[lines.length - 3], + lines[lines.length - 2], + lines[lines.length - 1] + }; + + int cups = Integer.parseInt(clue); + boolean water = false, milk = false, beans = false; + + for(String line : last3Lines) { + line = line.toLowerCase(); + + if(line.contains("milk")) { + milk = line.contains(Integer.toString(cups * 50)); + if (!milk) { + return new CheckResult(false, + "For the input " + clue + " your program output:\n\"" + + line + "\"\nbut the amount of milk should be " + (cups * 50)); + } + + } else if(line.contains("water")) { + water = line.contains(Integer.toString(cups * 200)); + if (!water) { + return new CheckResult(false, + "For the input " + clue + " your program output:\n" + + line + "\nbut the amount of water should be " + (cups * 200)); + } + + } else if(line.contains("beans")) { + beans = line.contains(Integer.toString(cups * 15)); + if (!beans) { + return new CheckResult(false, + "For the input " + clue + " your program output:\n" + + line + "\nbut the amount of beans should be " + (cups * 15)); + } + + + } else { + return new CheckResult(false, + "One of the last 3 lines " + + "doesn't contain \"milk\", \"water\" or \"beans\""); + } + } + + if (!water) { + return new CheckResult(false, + "There is no line with amount of water"); + } + + if (!milk) { + return new CheckResult(false, + "There is no line with amount of milk"); + } + + if (!beans) { + return new CheckResult(false, + "There is no line with amount of coffee beans"); + } + + return CheckResult.correct(); + } +} diff --git a/src/test/java/projects/python/coffee_machine/stage3/machine/coffee_machine.py b/src/test/java/projects/python/coffee_machine/stage3/machine/coffee_machine.py new file mode 100644 index 00000000..1d73eddd --- /dev/null +++ b/src/test/java/projects/python/coffee_machine/stage3/machine/coffee_machine.py @@ -0,0 +1,29 @@ +# Write your code here +water_amount = int(input('Write how many ml of water the coffee machine has:')) +milk_amount = int(input('Write how many ml of milk the coffee machine has:')) +coffee_amount = int(input('Write how many grams of coffee beans the coffee machine has:')) +N = int(water_amount / 200) +if N > milk_amount / 50: + N = int(milk_amount / 50) +if N > coffee_amount / 15: + N = int(coffee_amount / 15) +number_cups = int(input("Write how many cups of coffee you will need: ")) +if number_cups == N: + print("Yes, I can make that amount of coffee") +elif N > number_cups: + print("Yes, I can make that amount of coffee (and even ", N-1," more than that)") +else: + print("No, I can make only ", N," cups of coffee") +#print("""Starting to make a coffee +#Grinding coffee beans +#Boiling water +#Mixing boiled water with crushed coffee beans +#Pouring coffee into the cup +#Pouring some milk into the cup +#Coffee is ready!""") +# +# +#print("For ", number_cups, " cups of coffee you will need:") +#print(200 * number_cups, " ml of water") +#print(50 * number_cups, " ml of milk") +#print(15 * number_cups, " g of coffee beans") \ No newline at end of file diff --git a/src/test/java/projects/python/coffee_machine/stage3/test/CoffeeMachineTestPy3.java b/src/test/java/projects/python/coffee_machine/stage3/test/CoffeeMachineTestPy3.java new file mode 100644 index 00000000..851c75f6 --- /dev/null +++ b/src/test/java/projects/python/coffee_machine/stage3/test/CoffeeMachineTestPy3.java @@ -0,0 +1,167 @@ +package projects.python.coffee_machine.stage3.test; + +import org.hyperskill.hstest.stage.StageTest; +import org.hyperskill.hstest.testcase.CheckResult; +import org.hyperskill.hstest.testcase.TestCase; + +import java.util.List; + + +class TestClue { + boolean cond; + int num; + boolean showTests; + TestClue(boolean c, int n, boolean showTests) { + cond = c; + num = n; + this.showTests = showTests; + } +} + +public class CoffeeMachineTestPy3 extends StageTest { + + @Override + public List> generate() { + return List.of( + new TestCase() + .setAttach(new TestClue(true, 0, true)) + .setInput("300\n65\n111\n1"), + + new TestCase() + .setAttach(new TestClue(true, 2, true)) + .setInput("600\n153\n100\n1"), + + new TestCase() + .setAttach(new TestClue(true, 2, true)) + .setInput("1400\n150\n100\n1"), + + new TestCase() + .setAttach(new TestClue(true, 2, true)) + .setInput("1400\n1500\n45\n1"), + + new TestCase() + .setAttach(new TestClue(false, 2, true)) + .setInput("599\n250\n200\n10"), + + new TestCase() + .setAttach(new TestClue(true, 867, true)) + .setInput( "345640\n43423\n23234\n1"), + + new TestCase() + .setAttach(new TestClue(false, 1548, true)) + .setInput("345640\n434230\n23234\n19246"), + + new TestCase() + .setAttach(new TestClue(false, 172, true)) + .setInput( "34564\n43423\n23234\n19246"), + + new TestCase() + .setAttach(new TestClue(true, 0, false)) + .setInput("399\n112\n111\n1"), + + new TestCase() + .setAttach(new TestClue(true, 3, false)) + .setInput("2400\n249\n100\n1"), + + new TestCase() + .setAttach(new TestClue(true, 1, false)) + .setInput("1400\n1500\n44\n1"), + + new TestCase() + .setAttach(new TestClue(false, 2, false)) + .setInput("500\n250\n200\n10"), + + new TestCase() + .setAttach(new TestClue(true, 171, false)) + .setInput("34564\n43423\n23234\n1"), + + new TestCase() + .setAttach(new TestClue(true, 1547, false)) + .setInput("345640\n434230\n23234\n1"), + + new TestCase() + .setAttach(new TestClue(false, 868, false)) + .setInput("345640\n43423\n23234\n19246") + + ); + } + + @Override + public CheckResult check(String reply, TestClue clue) { + String[] lines = reply.trim().split(":"); + + if (lines.length <= 1) { + return CheckResult.wrong("Looks like you didn't print anything!"); + } + + String userOutput = lines[lines.length - 1].trim(); + String loweredOutput = userOutput.toLowerCase(); + boolean ans = clue.cond; + int amount = clue.num; + if (ans) { + if (amount > 0) { + boolean isCorrect = + loweredOutput.contains(Integer.toString(amount)) + && loweredOutput.contains("yes"); + + if (isCorrect) { + return CheckResult.correct(); + } else { + + String rightOutput = + "Yes, I can make that amount of coffee" + + " (and even " + amount + " more than that)"; + + if (clue.showTests) { + return new CheckResult(false, + "Your output:\n" + + userOutput + + "\nRight output:\n" + + rightOutput); + } else { + return CheckResult.wrong(""); + } + } + } + + String rightOutput = + "Yes, I can make that amount of coffee"; + + if (loweredOutput.equals(rightOutput.toLowerCase())) { + return CheckResult.correct(); + } else { + if (clue.showTests) { + return new CheckResult(false, + "Your output:\n" + + userOutput + + "\nRight output:\n" + + rightOutput); + } else { + return CheckResult.wrong(""); + } + } + + } else { + boolean cond1 = loweredOutput.contains("no"); + boolean cond2 = loweredOutput.contains(Integer.toString(amount)); + + if (cond1 && cond2) { + return CheckResult.correct(); + } else { + + String rightOutput = "No, I can make only " + + amount +" cup(s) of coffee"; + + if (clue.showTests) { + return new CheckResult(false, + "Your output:\n" + + userOutput + + "\nRight output:\n" + + rightOutput); + } else { + return CheckResult.wrong(""); + } + } + } + } +} diff --git a/src/test/java/projects/python/coffee_machine/stage4/machine/coffee_machine.py b/src/test/java/projects/python/coffee_machine/stage4/machine/coffee_machine.py new file mode 100644 index 00000000..875d1554 --- /dev/null +++ b/src/test/java/projects/python/coffee_machine/stage4/machine/coffee_machine.py @@ -0,0 +1,47 @@ +water = 400 +milk = 540 +beans = 120 +cups = 9 +money = 550 + +def machine_status(): + print(f'''The coffee machine has: +{water} of water +{milk} of milk +{beans} of coffee beans +{cups} of disposable cups +{money} of money''') + +machine_status() + +action = input("Write action (buy, fill, take):\n") + +if action == 'buy': + typ = int(input("What do you want to buy? 1 - espresso, 2 - latte, 3 - cappuccino:\n")) + if typ == 1: + water -= 250 + beans -= 16 + money += 4 + cups -= 1 + elif typ == 2: + water -= 350 + milk -= 75 + beans -= 20 + money += 7 + cups -= 1 + else: + water -= 200 + milk -= 100 + beans -= 12 + money += 6 + cups -= 1 +elif action == 'fill': + water += int(input("Write how many ml of water do you want to add:\n")) + milk += int(input("Write how many ml of milk do you want to add:\n")) + beans += int(input("Write how many grams of coffee beans do you want to add:\n")) + cups += int(input("Write how many disposable cups of coffee do you want to add:\n")) +elif action == 'take': + print(f"I gave you ${money}") + money = 0 + +machine_status() diff --git a/src/test/java/projects/python/coffee_machine/stage4/test/CoffeeMachineTestPy4.java b/src/test/java/projects/python/coffee_machine/stage4/test/CoffeeMachineTestPy4.java new file mode 100644 index 00000000..d446e11e --- /dev/null +++ b/src/test/java/projects/python/coffee_machine/stage4/test/CoffeeMachineTestPy4.java @@ -0,0 +1,346 @@ +package projects.python.coffee_machine.stage4.test; + +import org.hyperskill.hstest.stage.StageTest; +import org.hyperskill.hstest.testcase.CheckResult; +import org.hyperskill.hstest.testcase.TestCase; + +import java.util.ArrayList; +import java.util.List; + + +class TestClue { + String string; + TestClue(String s) { + string = s; + } +} + +public class CoffeeMachineTestPy4 extends StageTest { + + @Override + public List> generate() { + return List.of( + new TestCase() + .setAttach(new TestClue("take\n")) + .setInput("take\n"), + + new TestCase() + .setAttach(new TestClue("buy\n1\n")) + .setInput("buy\n1\n"), + + new TestCase() + .setAttach(new TestClue("buy\n2\n")) + .setInput("buy\n2\n"), + + new TestCase() + .setAttach(new TestClue("buy\n3\n")) + .setInput("buy\n3\n"), + + new TestCase() + .setAttach(new TestClue("fill\n2001\n510\n101\n21\n")) + .setInput("fill\n2001\n510\n101\n21\n") + ); + } + + @Override + public CheckResult check(String reply, TestClue clue) { + String[] lines = reply.trim().split("\\n"); + + if (lines.length <= 1) { + return CheckResult.wrong("Looks like you didn't print anything!"); + } + + String[] clueLines = clue.string.trim().split("\\n"); + String action = clueLines[0].trim(); + + List milk = new ArrayList<>(); + List water = new ArrayList<>(); + List beans = new ArrayList<>(); + List cups = new ArrayList<>(); + List money = new ArrayList<>(); + + for (String line : lines) { + String[] words = line.split("\\s+"); + if (words.length == 0) { + continue; + } + String firstWord = words[0].replace("$", ""); + int amount; + try { + amount = Integer.parseInt(firstWord); + } + catch (Exception e) { + continue; + } + if (line.contains("milk")) { + milk.add(amount); + } + else if (line.contains("water")) { + water.add(amount); + } + else if (line.contains("beans")) { + beans.add(amount); + } + else if (line.contains("cups")) { + cups.add(amount); + } + else if (line.contains("money")) { + money.add(amount); + } + } + + if (milk.size() != 2) { + return new CheckResult(false, + "There should be two lines with \"milk\", " + + "found: " + milk.size()); + } + + if (water.size() != 2) { + return new CheckResult(false, + "There should be two lines with \"water\", " + + "found: " + water.size()); + } + + if (beans.size() != 2) { + return new CheckResult(false, + "There should be two lines with \"beans\", " + + "found: " + beans.size()); + } + + if (cups.size() != 2) { + return new CheckResult(false, + "There should be two lines with \"cups\", " + + "found: " + cups.size()); + } + + if (money.size() != 2) { + return new CheckResult(false, + "There should be two lines with \"money\", " + + "found: " + money.size()); + } + + + int milk0 = milk.get(0); + int milk1 = milk.get(milk.size() - 1); + + int water0 = water.get(0); + int water1 = water.get(water.size() - 1); + + int beans0 = beans.get(0); + int beans1 = beans.get(beans.size() - 1); + + int cups0 = cups.get(0); + int cups1 = cups.get(cups.size() - 1); + + int money0 = money.get(0); + int money1 = money.get(money.size() - 1); + + if (water0 != 400 || milk0 != 540 || beans0 != 120 + || cups0 != 9 || money0 != 550) { + return new CheckResult(false, + "Initial setup is wrong: " + + "coffee machine should fe filled like " + + "stated in the description"); + } + + int diffWater = water1 - water0; + int diffMilk = milk1 - milk0; + int diffBeans = beans1 - beans0; + int diffCups = cups1 - cups0; + int diffMoney = money1 - money0; + + if (action.equals("take")) { + + if (diffMilk != 0) { + return new CheckResult(false, + "After \"take\" action milk " + + "amount shouldn't be changed"); + } + + if (diffWater != 0) { + return new CheckResult(false, + "After \"take\" action water " + + "amount shouldn't be changed"); + } + + if (diffBeans != 0) { + return new CheckResult(false, + "After \"take\" action beans " + + "amount shouldn't be changed"); + } + + if (diffCups != 0) { + return new CheckResult(false, + "After \"take\" action cups " + + "amount shouldn't be changed"); + } + + if (money1 != 0) { + return new CheckResult(false, + "After \"take\" action money " + + "amount should be zero"); + } + + return CheckResult.correct(); + } + + else if (action.equals("buy")) { + + String option = clueLines[1].trim(); + + if (option.equals("1")) { + + if (diffWater != -250) { + return new CheckResult(false, + "After buying the first option " + + "water amount should be lowered by 250"); + } + + if (diffMilk != 0) { + return new CheckResult(false, + "After buying the first option " + + "milk amount should not be changed"); + } + + if (diffBeans != -16) { + return new CheckResult(false, + "After buying the first option " + + "beans amount should be lowered by 16"); + } + + if (diffCups != -1) { + return new CheckResult(false, + "After buying the first option " + + "cups amount should be lowered by 1"); + } + + if (diffMoney != 4) { + return new CheckResult(false, + "After buying the first option " + + "money amount should be increased by 4"); + } + + return CheckResult.correct(); + + } + + else if (option.equals("2")) { + + if (diffWater != -350) { + return new CheckResult(false, + "After buying the second option " + + "water amount should be lowered by 350"); + } + + if (diffMilk != -75) { + return new CheckResult(false, + "After buying the second option " + + "milk amount should be lowered by 75"); + } + + if (diffBeans != -20) { + return new CheckResult(false, + "After buying the second option " + + "beans amount should be lowered by 20"); + } + + if (diffCups != -1) { + return new CheckResult(false, + "After buying the second option " + + "cups amount should be lowered by 1"); + } + + if (diffMoney != 7) { + return new CheckResult(false, + "After buying the second option " + + "money amount should be increased by 7"); + } + + return CheckResult.correct(); + } + + else if (option.equals("3")) { + + if (diffWater != -200) { + return new CheckResult(false, + "After buying the third option " + + "water amount should be lowered by 350"); + } + + if (diffMilk != -100) { + return new CheckResult(false, + "After buying the third option " + + "milk amount should be lowered by 75"); + } + + if (diffBeans != -12) { + return new CheckResult(false, + "After buying the third option " + + "beans amount should be lowered by 20"); + } + + if (diffCups != -1) { + return new CheckResult(false, + "After buying the third option " + + "cups amount should be lowered by 1"); + } + + if (diffMoney != 6) { + return new CheckResult(false, + "After buying the third option " + + "money amount should be increased by 7"); + } + + return CheckResult.correct(); + } + } + + else if (action.equals("fill")) { + + int water_ = Integer.parseInt(clueLines[1]); + int milk_ = Integer.parseInt(clueLines[2]); + int beans_ = Integer.parseInt(clueLines[3]); + int cups_ = Integer.parseInt(clueLines[4]); + + if (diffMoney != 0) { + return new CheckResult(false, + "After \"fill\" action " + + "money amount should not be changed"); + } + + if (diffWater != water_) { + return new CheckResult(false, + "After \"fill\" action " + + "water amount expected to be increased by " + water_ + + " but was increased by " + diffWater); + } + + if (diffMilk != milk_) { + return new CheckResult(false, + "After \"fill\" action " + + "milk amount expected to be increased by " + milk_ + + " but was increased by " + diffMilk); + } + + if (diffBeans != beans_) { + return new CheckResult(false, + "After \"fill\" action " + + "beans amount expected to be increased by " + beans_ + + " but was increased by " + diffBeans); + } + + if (diffCups != cups_) { + return new CheckResult(false, + "After \"fill\" action " + + "cups amount expected to be increased by " + cups_ + + " but was increased by " + diffCups); + } + + + return CheckResult.correct(); + + } + + throw new RuntimeException("Can't check the answer"); + } +} diff --git a/src/test/java/projects/python/coffee_machine/stage5/machine/coffee_machine.py b/src/test/java/projects/python/coffee_machine/stage5/machine/coffee_machine.py new file mode 100644 index 00000000..50975b9d --- /dev/null +++ b/src/test/java/projects/python/coffee_machine/stage5/machine/coffee_machine.py @@ -0,0 +1,113 @@ +# ACTIONS +BUY = "buy" +FILL = "fill" +TAKE = "take" +REMAINING = "remaining" +EXIT_ = "exit" + +# COFFEE PRICES +# 0 - water, 1 - milk, 2 - beans, 4 - cus, 5 - cost +ESPRESSO_NEEDS = [250, 0, 16, 1, 4] +LATTE_NEEDS = [350, 75, 20, 1, 7] +CAPPUCCINO_NEEDS = [200, 100, 12, 1, 6] +# INITIAL MACHINE STATE +machine_state = [400, 540, 120, 9, 550] +machine_desc = ["water", "milk", "coffee beans", "disposable cups", "money"] + + +def print_machine_state(): + print("The coffee machine has:") + for i in range(len(machine_state)): + print(machine_state[i], "of " + machine_desc[i]) + + +def action_handle(user_action_def): + if user_action_def == BUY: + return buy_coffee() + elif user_action_def == FILL: + return fill_machine(machine_state) + elif user_action_def == TAKE: + return take_money(machine_state) + elif user_action_def == REMAINING: + return print_machine_state() + + +def buy_coffee(): + print("What do you want to buy? " + + "1 - espresso, 2 - latte, 3 - cappuccino, back - to main menu:") + user_choice = input() + if user_choice == "back": + return False + else: + user_choice = int(user_choice) + + if not check_resources(user_choice): + return False + if user_choice == 1: + change_machine_state(machine_state, ESPRESSO_NEEDS) + elif user_choice == 2: + change_machine_state(machine_state, LATTE_NEEDS) + elif user_choice == 3: + change_machine_state(machine_state, CAPPUCCINO_NEEDS) + + +def check_resources(user_choice): + if user_choice == 1: + for i in range(len(machine_state)): + if machine_state[i] < ESPRESSO_NEEDS[i]: + print("Sorry, not enough " + machine_desc[i] + "!") + return False + elif user_choice == 2: + for i in range(len(machine_state)): + if machine_state[i] < LATTE_NEEDS[i]: + print("Sorry, not enough " + machine_desc[i] + "!") + return False + elif user_choice == 3: + for i in range(len(machine_state)): + if machine_state[i] < CAPPUCCINO_NEEDS[i]: + print("Sorry, not enough " + machine_desc[i] + "!") + return False + else: + print("Unknown case!") + return False + + print("I have enough resources, making you a coffee!") + return True + + +def change_machine_state(machine, coffee_cost): + for i in range(len(machine)-1): + machine[i] -= coffee_cost[i] + # print(machine[i]) + # add money + machine[len(machine)-1] += coffee_cost[len(machine)-1] + + +def fill_machine(machine): + print("Write how many ml of water do you want to add:") + fill_water = int(input()) + print("Write how many ml of milk do you want to add:") + fill_milk = int(input()) + print("Write how many grams of coffee beans do you want to add:") + fill_beans = int(input()) + print("Write how many disposable cups of coffee do you want to add:") + fill_cups = int(input()) + + machine[0] += fill_water + machine[1] += fill_milk + machine[2] += fill_beans + machine[3] += fill_cups + + +def take_money(machine): + print("I gave you $" + str(machine[4])) + machine[4] = 0 + + +while True: + print() + print("Write action (buy, fill, take, remaining, exit):") + user_action = input() + if user_action == EXIT_: + break + action_handle(user_action) diff --git a/src/test/java/projects/python/coffee_machine/stage5/test/CoffeeMachineTestPy5.java b/src/test/java/projects/python/coffee_machine/stage5/test/CoffeeMachineTestPy5.java new file mode 100644 index 00000000..a52f6795 --- /dev/null +++ b/src/test/java/projects/python/coffee_machine/stage5/test/CoffeeMachineTestPy5.java @@ -0,0 +1,263 @@ +package projects.python.coffee_machine.stage5.test; + +import org.hyperskill.hstest.stage.StageTest; +import org.hyperskill.hstest.testcase.CheckResult; +import org.hyperskill.hstest.testcase.TestCase; + +import java.util.ArrayList; +import java.util.List; + + +class TestClue { + int water; + int milk; + int beans; + int cups; + int money; + String feedback; + TestClue(int w, int m, int b, int c, int mo, String feedback) { + water = w; + milk = m; + beans = b; + cups = c; + money = mo; + this.feedback = feedback; + } +} + +public class CoffeeMachineTestPy5 extends StageTest { + + @Override + public List> generate() { + return List.of( + new TestCase() + .setAttach(new TestClue( + 700 - 400, + 390 - 540, + 80 - 120 , + 7 - 9, + 0 - 550, + "This test is exactly " + + "like in the example - try to run it by yourself")) + .setInput( + "remaining\n" + + "buy\n" + + "2\n" + + "buy\n" + + "2\n" + + "fill\n" + + "1000\n" + + "0\n" + + "0\n" + + "0\n" + + "buy\n" + + "2\n" + + "take\n" + + "remaining\n" + + "exit\n"), + + new TestCase() + .setAttach(new TestClue( + 3000, + 3000, + 3000 , + 3000, + 0, + "This test checks \"fill\" action")) + .setInput( + "remaining\n" + + "fill\n" + + "3000\n" + + "3000\n" + + "3000\n" + + "3000\n" + + "remaining\n" + + "exit\n"), + + new TestCase() + .setAttach(new TestClue( + -250, + 0, + -16 , + -1, + 4, "This test checks \"buy\" " + + "action with the first variant of coffee")) + .setInput( + "remaining\n" + + "buy\n" + + "1\n" + + "remaining\n" + + "exit\n"), + + new TestCase() + .setAttach(new TestClue( + -350, + -75, + -20 , + -1, + 7, "This test checks \"buy\" " + + "action with the second variant of coffee")) + .setInput( + "remaining\n" + + "buy\n" + + "2\n" + + "remaining\n" + + "exit\n"), + + new TestCase() + .setAttach(new TestClue( + -200, + -100, + -12 , + -1, + 6, "This test checks \"buy\" " + + "action with the third variant of coffee")) + .setInput( + "remaining\n" + + "buy\n" + + "3\n" + + "remaining\n" + + "exit\n"), + + new TestCase() + .setAttach(new TestClue( + 0, + 0, + 0 , + 0, + -550, "This test checks \"take\" action")) + .setInput( + "remaining\n" + + "take\n" + + "remaining\n" + + "exit\n"), + + new TestCase() + .setAttach(new TestClue( + 0, + 0, + 0 , + 0, + 0, "This test checks \"back\" " + + "action right after \"buy\" action")) + .setInput( + "remaining\n" + + "buy\n" + + "back\n" + + "remaining\n" + + "exit\n") + ); + } + + @Override + public CheckResult check(String reply, TestClue clue) { + String[] lines = reply.split("\\n"); + + if (lines.length <= 1) { + return CheckResult.wrong("Looks like you didn't print anything!"); + } + + int water_ = clue.water; + int milk_ = clue.milk; + int beans_ = clue.beans; + int cups_ = clue.cups; + int money_ = clue.money; + + List milk = new ArrayList<>(); + List water = new ArrayList<>(); + List beans = new ArrayList<>(); + List cups = new ArrayList<>(); + List money = new ArrayList<>(); + + for (String line : lines) { + line = line.replace("$", "").trim(); + String[] words = line.split("\\s+"); + if (words.length == 0) { + continue; + } + String firstWord = words[0]; + int amount; + try { + amount = Integer.parseInt(firstWord); + } + catch (Exception e) { + continue; + } + if (line.contains("milk")) { + milk.add(amount); + } + else if (line.contains("water")) { + water.add(amount); + } + else if (line.contains("beans")) { + beans.add(amount); + } + else if (line.contains("cups")) { + cups.add(amount); + } + else if (line.contains("money")) { + money.add(amount); + } + } + + if (milk.size() != 2) { + return new CheckResult(false, + "There should be two lines with \"milk\", " + + "found: " + milk.size()); + } + + if (water.size() != 2) { + return new CheckResult(false, + "There should be two lines with \"water\", " + + "found: " + water.size()); + } + + if (beans.size() != 2) { + return new CheckResult(false, + "There should be two lines with \"beans\", " + + "found: " + beans.size()); + } + + if (cups.size() != 2) { + return new CheckResult(false, + "There should be two lines with \"cups\", " + + "found: " + cups.size()); + } + + if (money.size() != 2) { + return new CheckResult(false, + "There should be two lines with \"money\", " + + "found: " + money.size()); + } + + int milk0 = milk.get(0); + int milk1 = milk.get(milk.size() - 1); + + int water0 = water.get(0); + int water1 = water.get(water.size() - 1); + + int beans0 = beans.get(0); + int beans1 = beans.get(beans.size() - 1); + + int cups0 = cups.get(0); + int cups1 = cups.get(cups.size() - 1); + + int money0 = money.get(0); + int money1 = money.get(money.size() - 1); + + int diffWater = water1 - water0; + int diffMilk = milk1 - milk0; + int diffBeans = beans1 - beans0; + int diffCups = cups1 - cups0; + int diffMoney = money1 - money0; + + boolean isCorrect = + diffWater == water_ && + diffMilk == milk_ && + diffBeans == beans_ && + diffCups == cups_ && + diffMoney == money_; + + return new CheckResult(isCorrect, clue.feedback); + } +} From 94ba1983aef9e45800d48488cc29795438763555 Mon Sep 17 00:00:00 2001 From: Vladimir Turov Date: Thu, 28 Oct 2021 19:09:20 +0300 Subject: [PATCH 14/55] Fix tests detection --- .../org/hyperskill/hstest/common/ReflectionUtils.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/hyperskill/hstest/common/ReflectionUtils.java b/src/main/java/org/hyperskill/hstest/common/ReflectionUtils.java index 960a28fb..1397d052 100644 --- a/src/main/java/org/hyperskill/hstest/common/ReflectionUtils.java +++ b/src/main/java/org/hyperskill/hstest/common/ReflectionUtils.java @@ -227,15 +227,15 @@ public static List getAllFields(Object obj) { } public static > boolean isTests(T stage) throws URISyntaxException { - return new File(stage + var clazz = new File(stage .getClass() .getProtectionDomain() .getCodeSource() .getLocation().toURI()) - .getAbsolutePath() - .contains(File.separator + "hs-test" - + File.separator + "build" - + File.separator + "classes"); + .getAbsolutePath(); + + return clazz.contains(File.separator + "hs-test" + File.separator + "build") + || clazz.contains(File.separator + "hs-test" + File.separator + "out"); } public static > void setupCwd(T stage) { From be3d4035aa8b291e9c3f93064f2d8953769b936c Mon Sep 17 00:00:00 2001 From: Vladimir Turov Date: Thu, 28 Oct 2021 19:18:40 +0300 Subject: [PATCH 15/55] Fix dynamic testing working in windows only --- .../org/hyperskill/hstest/testing/ProcessWrapper.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/hyperskill/hstest/testing/ProcessWrapper.java b/src/main/java/org/hyperskill/hstest/testing/ProcessWrapper.java index f1be3d60..16a88ee7 100644 --- a/src/main/java/org/hyperskill/hstest/testing/ProcessWrapper.java +++ b/src/main/java/org/hyperskill/hstest/testing/ProcessWrapper.java @@ -3,6 +3,7 @@ import lombok.Getter; import lombok.Setter; import org.hyperskill.hstest.common.FileUtils; +import org.hyperskill.hstest.common.OsUtils; import org.hyperskill.hstest.dynamic.security.ExitException; import org.hyperskill.hstest.exception.outcomes.UnexpectedError; import org.hyperskill.hstest.stage.StageTest; @@ -13,8 +14,6 @@ import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; -import java.util.Arrays; -import java.util.LinkedList; import java.util.List; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; @@ -92,8 +91,12 @@ public ProcessWrapper start() { try { List fullArgs = new ArrayList<>(); - fullArgs.add("cmd"); - fullArgs.add("/c"); + + if (OsUtils.isWindows()) { + fullArgs.add("cmd"); + fullArgs.add("/c"); + } + fullArgs.addAll(List.of(args)); process = new ProcessBuilder(fullArgs) From 48694938e602de2398cd4def9dd38641a6d3bc0e Mon Sep 17 00:00:00 2001 From: Vladimir Turov Date: Thu, 11 Nov 2021 00:47:47 +0300 Subject: [PATCH 16/55] Skip process tests if they are not explicitly required via -D... flag --- .../hyperskill/hstest/testing/ExecutionOptions.java | 13 +++++++++++++ .../hyperskill/hstest/testing/ProcessWrapper.java | 5 +++-- .../stage1/test/CoffeeMachineTestGo1.java | 9 +++++++++ .../stage1_ce/test/CoffeeMachineTestGo1Ce.java | 9 +++++++++ .../stage1_ex/test/CoffeeMachineTestGo1Ex.java | 9 +++++++++ .../stage1_wa/test/CoffeeMachineTestGo1Wa.java | 9 +++++++++ .../stage2/test/CoffeeMachineTestGo2.java | 9 +++++++++ .../stage3/test/CoffeeMachineTestGo3.java | 9 +++++++++ .../stage4/test/CoffeeMachineTestGo4.java | 9 +++++++++ .../stage5/test/CoffeeMachineTestGo5.java | 9 +++++++++ .../stage1/test/CoffeeMachineTestJs1.java | 9 +++++++++ .../stage1_ce/test/CoffeeMachineTestJs1Ce.java | 9 +++++++++ .../stage1_ex/test/CoffeeMachineTestJs1Ex.java | 9 +++++++++ .../stage1_wa/test/CoffeeMachineTestJs1Wa.java | 9 +++++++++ .../stage2/test/CoffeeMachineTestJs2.java | 9 +++++++++ .../stage3/test/CoffeeMachineTestJs3.java | 9 +++++++++ .../stage4/test/CoffeeMachineTestJs4.java | 9 +++++++++ .../stage5/test/CoffeeMachineTestJs5.java | 9 +++++++++ .../stage1/test/CoffeeMachineTestPy1.java | 9 +++++++++ .../stage1_ce/test/CoffeeMachineTestPy1Ce.java | 9 +++++++++ .../stage1_ex/test/CoffeeMachineTestPy1Ex.java | 9 +++++++++ .../stage1_wa/test/CoffeeMachineTestPy1Wa.java | 9 +++++++++ .../stage2/test/CoffeeMachineTestPy2.java | 9 +++++++++ .../stage3/test/CoffeeMachineTestPy3.java | 9 +++++++++ .../stage4/test/CoffeeMachineTestPy4.java | 9 +++++++++ .../stage5/test/CoffeeMachineTestPy5.java | 9 +++++++++ 26 files changed, 232 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/hyperskill/hstest/testing/ExecutionOptions.java b/src/main/java/org/hyperskill/hstest/testing/ExecutionOptions.java index 2070bba5..a595c499 100644 --- a/src/main/java/org/hyperskill/hstest/testing/ExecutionOptions.java +++ b/src/main/java/org/hyperskill/hstest/testing/ExecutionOptions.java @@ -49,4 +49,17 @@ private ExecutionOptions() { } * Use "-DforceSecurityManager=true" to set this flag. */ public static boolean forceSecurityManager = Boolean.getBoolean("forceSecurityManager"); + + /** + * Enables tests that use process testing to test solutions written in Go, JS etc. + * By default, it's turned off because some computers don't have Go, JS etc. installed. + * For example, Jitpack, that builds hs-test library. + * + * To mark the test as the one that uses process testing you should write the following code: + * \@BeforeClass + * public static void stopProcessTest() { + * Assume.assumeTrue(includeProcessTesting); + * } + */ + public static boolean includeProcessTesting = Boolean.getBoolean("includeProcessTesting"); } diff --git a/src/main/java/org/hyperskill/hstest/testing/ProcessWrapper.java b/src/main/java/org/hyperskill/hstest/testing/ProcessWrapper.java index 16a88ee7..4d626f51 100644 --- a/src/main/java/org/hyperskill/hstest/testing/ProcessWrapper.java +++ b/src/main/java/org/hyperskill/hstest/testing/ProcessWrapper.java @@ -93,8 +93,9 @@ public ProcessWrapper start() { List fullArgs = new ArrayList<>(); if (OsUtils.isWindows()) { - fullArgs.add("cmd"); - fullArgs.add("/c"); + // To test this in windows you need WSL2 installed + fullArgs.add("bash"); + fullArgs.add("-c"); } fullArgs.addAll(List.of(args)); diff --git a/src/test/java/projects/go/coffee_machine/stage1/test/CoffeeMachineTestGo1.java b/src/test/java/projects/go/coffee_machine/stage1/test/CoffeeMachineTestGo1.java index c56a6548..6672bb71 100644 --- a/src/test/java/projects/go/coffee_machine/stage1/test/CoffeeMachineTestGo1.java +++ b/src/test/java/projects/go/coffee_machine/stage1/test/CoffeeMachineTestGo1.java @@ -3,12 +3,21 @@ import org.hyperskill.hstest.stage.StageTest; import org.hyperskill.hstest.testcase.CheckResult; import org.hyperskill.hstest.testcase.TestCase; +import org.junit.Assume; +import org.junit.BeforeClass; import java.util.List; +import static org.hyperskill.hstest.testing.ExecutionOptions.includeProcessTesting; + public class CoffeeMachineTestGo1 extends StageTest { + @BeforeClass + public static void stopProcessTest() { + Assume.assumeTrue(includeProcessTesting); + } + @Override public List> generate() { return List.of( diff --git a/src/test/java/projects/go/coffee_machine/stage1_ce/test/CoffeeMachineTestGo1Ce.java b/src/test/java/projects/go/coffee_machine/stage1_ce/test/CoffeeMachineTestGo1Ce.java index 3f6661d9..98939dab 100644 --- a/src/test/java/projects/go/coffee_machine/stage1_ce/test/CoffeeMachineTestGo1Ce.java +++ b/src/test/java/projects/go/coffee_machine/stage1_ce/test/CoffeeMachineTestGo1Ce.java @@ -2,14 +2,23 @@ import org.hyperskill.hstest.testcase.CheckResult; import org.hyperskill.hstest.testcase.TestCase; +import org.junit.Assume; +import org.junit.BeforeClass; import outcomes.base.ContainsMessage; import outcomes.base.UserErrorTest; import java.util.List; +import static org.hyperskill.hstest.testing.ExecutionOptions.includeProcessTesting; + public class CoffeeMachineTestGo1Ce extends UserErrorTest { + @BeforeClass + public static void stopProcessTest() { + Assume.assumeTrue(includeProcessTesting); + } + @ContainsMessage String msg = "Compilation error\n" + "\n" + diff --git a/src/test/java/projects/go/coffee_machine/stage1_ex/test/CoffeeMachineTestGo1Ex.java b/src/test/java/projects/go/coffee_machine/stage1_ex/test/CoffeeMachineTestGo1Ex.java index 30b022fe..f5d8fa6f 100644 --- a/src/test/java/projects/go/coffee_machine/stage1_ex/test/CoffeeMachineTestGo1Ex.java +++ b/src/test/java/projects/go/coffee_machine/stage1_ex/test/CoffeeMachineTestGo1Ex.java @@ -2,14 +2,23 @@ import org.hyperskill.hstest.testcase.CheckResult; import org.hyperskill.hstest.testcase.TestCase; +import org.junit.Assume; +import org.junit.BeforeClass; import outcomes.base.ContainsMessage; import outcomes.base.UserErrorTest; import java.util.List; +import static org.hyperskill.hstest.testing.ExecutionOptions.includeProcessTesting; + public class CoffeeMachineTestGo1Ex extends UserErrorTest { + @BeforeClass + public static void stopProcessTest() { + Assume.assumeTrue(includeProcessTesting); + } + @ContainsMessage String msg = "Exception in test #1\n" + "\n" + diff --git a/src/test/java/projects/go/coffee_machine/stage1_wa/test/CoffeeMachineTestGo1Wa.java b/src/test/java/projects/go/coffee_machine/stage1_wa/test/CoffeeMachineTestGo1Wa.java index a9c540d7..458f8fbe 100644 --- a/src/test/java/projects/go/coffee_machine/stage1_wa/test/CoffeeMachineTestGo1Wa.java +++ b/src/test/java/projects/go/coffee_machine/stage1_wa/test/CoffeeMachineTestGo1Wa.java @@ -2,14 +2,23 @@ import org.hyperskill.hstest.testcase.CheckResult; import org.hyperskill.hstest.testcase.TestCase; +import org.junit.Assume; +import org.junit.BeforeClass; import outcomes.base.ContainsMessage; import outcomes.base.UserErrorTest; import java.util.List; +import static org.hyperskill.hstest.testing.ExecutionOptions.includeProcessTesting; + public class CoffeeMachineTestGo1Wa extends UserErrorTest { + @BeforeClass + public static void stopProcessTest() { + Assume.assumeTrue(includeProcessTesting); + } + @ContainsMessage String msg = "Wrong answer in test #1\n" + "\n" + diff --git a/src/test/java/projects/go/coffee_machine/stage2/test/CoffeeMachineTestGo2.java b/src/test/java/projects/go/coffee_machine/stage2/test/CoffeeMachineTestGo2.java index 16e5c484..e524cf77 100644 --- a/src/test/java/projects/go/coffee_machine/stage2/test/CoffeeMachineTestGo2.java +++ b/src/test/java/projects/go/coffee_machine/stage2/test/CoffeeMachineTestGo2.java @@ -3,12 +3,21 @@ import org.hyperskill.hstest.stage.StageTest; import org.hyperskill.hstest.testcase.CheckResult; import org.hyperskill.hstest.testcase.TestCase; +import org.junit.Assume; +import org.junit.BeforeClass; import java.util.List; +import static org.hyperskill.hstest.testing.ExecutionOptions.includeProcessTesting; + public class CoffeeMachineTestGo2 extends StageTest { + @BeforeClass + public static void stopProcessTest() { + Assume.assumeTrue(includeProcessTesting); + } + @Override public List> generate() { return List.of( diff --git a/src/test/java/projects/go/coffee_machine/stage3/test/CoffeeMachineTestGo3.java b/src/test/java/projects/go/coffee_machine/stage3/test/CoffeeMachineTestGo3.java index d2709353..935b67b0 100644 --- a/src/test/java/projects/go/coffee_machine/stage3/test/CoffeeMachineTestGo3.java +++ b/src/test/java/projects/go/coffee_machine/stage3/test/CoffeeMachineTestGo3.java @@ -3,9 +3,13 @@ import org.hyperskill.hstest.stage.StageTest; import org.hyperskill.hstest.testcase.CheckResult; import org.hyperskill.hstest.testcase.TestCase; +import org.junit.Assume; +import org.junit.BeforeClass; import java.util.List; +import static org.hyperskill.hstest.testing.ExecutionOptions.includeProcessTesting; + class TestClue { boolean cond; @@ -20,6 +24,11 @@ class TestClue { public class CoffeeMachineTestGo3 extends StageTest { + @BeforeClass + public static void stopProcessTest() { + Assume.assumeTrue(includeProcessTesting); + } + @Override public List> generate() { return List.of( diff --git a/src/test/java/projects/go/coffee_machine/stage4/test/CoffeeMachineTestGo4.java b/src/test/java/projects/go/coffee_machine/stage4/test/CoffeeMachineTestGo4.java index 7e057ba0..005dc60f 100644 --- a/src/test/java/projects/go/coffee_machine/stage4/test/CoffeeMachineTestGo4.java +++ b/src/test/java/projects/go/coffee_machine/stage4/test/CoffeeMachineTestGo4.java @@ -3,10 +3,14 @@ import org.hyperskill.hstest.stage.StageTest; import org.hyperskill.hstest.testcase.CheckResult; import org.hyperskill.hstest.testcase.TestCase; +import org.junit.Assume; +import org.junit.BeforeClass; import java.util.ArrayList; import java.util.List; +import static org.hyperskill.hstest.testing.ExecutionOptions.includeProcessTesting; + class TestClue { String string; @@ -17,6 +21,11 @@ class TestClue { public class CoffeeMachineTestGo4 extends StageTest { + @BeforeClass + public static void stopProcessTest() { + Assume.assumeTrue(includeProcessTesting); + } + @Override public List> generate() { return List.of( diff --git a/src/test/java/projects/go/coffee_machine/stage5/test/CoffeeMachineTestGo5.java b/src/test/java/projects/go/coffee_machine/stage5/test/CoffeeMachineTestGo5.java index 49ab5c1b..a62c5536 100644 --- a/src/test/java/projects/go/coffee_machine/stage5/test/CoffeeMachineTestGo5.java +++ b/src/test/java/projects/go/coffee_machine/stage5/test/CoffeeMachineTestGo5.java @@ -3,10 +3,14 @@ import org.hyperskill.hstest.stage.StageTest; import org.hyperskill.hstest.testcase.CheckResult; import org.hyperskill.hstest.testcase.TestCase; +import org.junit.Assume; +import org.junit.BeforeClass; import java.util.ArrayList; import java.util.List; +import static org.hyperskill.hstest.testing.ExecutionOptions.includeProcessTesting; + class TestClue { int water; @@ -27,6 +31,11 @@ class TestClue { public class CoffeeMachineTestGo5 extends StageTest { + @BeforeClass + public static void stopProcessTest() { + Assume.assumeTrue(includeProcessTesting); + } + @Override public List> generate() { return List.of( diff --git a/src/test/java/projects/javascript/coffee_machine/stage1/test/CoffeeMachineTestJs1.java b/src/test/java/projects/javascript/coffee_machine/stage1/test/CoffeeMachineTestJs1.java index cd93f323..822e844b 100644 --- a/src/test/java/projects/javascript/coffee_machine/stage1/test/CoffeeMachineTestJs1.java +++ b/src/test/java/projects/javascript/coffee_machine/stage1/test/CoffeeMachineTestJs1.java @@ -3,12 +3,21 @@ import org.hyperskill.hstest.stage.StageTest; import org.hyperskill.hstest.testcase.CheckResult; import org.hyperskill.hstest.testcase.TestCase; +import org.junit.Assume; +import org.junit.BeforeClass; import java.util.List; +import static org.hyperskill.hstest.testing.ExecutionOptions.includeProcessTesting; + public class CoffeeMachineTestJs1 extends StageTest { + @BeforeClass + public static void stopProcessTest() { + Assume.assumeTrue(includeProcessTesting); + } + @Override public List> generate() { return List.of( diff --git a/src/test/java/projects/javascript/coffee_machine/stage1_ce/test/CoffeeMachineTestJs1Ce.java b/src/test/java/projects/javascript/coffee_machine/stage1_ce/test/CoffeeMachineTestJs1Ce.java index af3a6c5c..15bb09e9 100644 --- a/src/test/java/projects/javascript/coffee_machine/stage1_ce/test/CoffeeMachineTestJs1Ce.java +++ b/src/test/java/projects/javascript/coffee_machine/stage1_ce/test/CoffeeMachineTestJs1Ce.java @@ -2,14 +2,23 @@ import org.hyperskill.hstest.testcase.CheckResult; import org.hyperskill.hstest.testcase.TestCase; +import org.junit.Assume; +import org.junit.BeforeClass; import outcomes.base.ContainsMessage; import outcomes.base.UserErrorTest; import java.util.List; +import static org.hyperskill.hstest.testing.ExecutionOptions.includeProcessTesting; + public class CoffeeMachineTestJs1Ce extends UserErrorTest { + @BeforeClass + public static void stopProcessTest() { + Assume.assumeTrue(includeProcessTesting); + } + @ContainsMessage String msg = "main.js:2\n" + "console.log(`Starting to make a coffee"; diff --git a/src/test/java/projects/javascript/coffee_machine/stage1_ex/test/CoffeeMachineTestJs1Ex.java b/src/test/java/projects/javascript/coffee_machine/stage1_ex/test/CoffeeMachineTestJs1Ex.java index 3c2cb8c2..769e40af 100644 --- a/src/test/java/projects/javascript/coffee_machine/stage1_ex/test/CoffeeMachineTestJs1Ex.java +++ b/src/test/java/projects/javascript/coffee_machine/stage1_ex/test/CoffeeMachineTestJs1Ex.java @@ -2,14 +2,23 @@ import org.hyperskill.hstest.testcase.CheckResult; import org.hyperskill.hstest.testcase.TestCase; +import org.junit.Assume; +import org.junit.BeforeClass; import outcomes.base.ContainsMessage; import outcomes.base.UserErrorTest; import java.util.List; +import static org.hyperskill.hstest.testing.ExecutionOptions.includeProcessTesting; + public class CoffeeMachineTestJs1Ex extends UserErrorTest { + @BeforeClass + public static void stopProcessTest() { + Assume.assumeTrue(includeProcessTesting); + } + @ContainsMessage String msg = "Exception in test #1"; diff --git a/src/test/java/projects/javascript/coffee_machine/stage1_wa/test/CoffeeMachineTestJs1Wa.java b/src/test/java/projects/javascript/coffee_machine/stage1_wa/test/CoffeeMachineTestJs1Wa.java index 5d1aa041..1895f8ce 100644 --- a/src/test/java/projects/javascript/coffee_machine/stage1_wa/test/CoffeeMachineTestJs1Wa.java +++ b/src/test/java/projects/javascript/coffee_machine/stage1_wa/test/CoffeeMachineTestJs1Wa.java @@ -2,14 +2,23 @@ import org.hyperskill.hstest.testcase.CheckResult; import org.hyperskill.hstest.testcase.TestCase; +import org.junit.Assume; +import org.junit.BeforeClass; import outcomes.base.ContainsMessage; import outcomes.base.UserErrorTest; import java.util.List; +import static org.hyperskill.hstest.testing.ExecutionOptions.includeProcessTesting; + public class CoffeeMachineTestJs1Wa extends UserErrorTest { + @BeforeClass + public static void stopProcessTest() { + Assume.assumeTrue(includeProcessTesting); + } + @ContainsMessage String msg = "Wrong answer in test #1\n" + "\n" + diff --git a/src/test/java/projects/javascript/coffee_machine/stage2/test/CoffeeMachineTestJs2.java b/src/test/java/projects/javascript/coffee_machine/stage2/test/CoffeeMachineTestJs2.java index 8adcdfb8..d986acce 100644 --- a/src/test/java/projects/javascript/coffee_machine/stage2/test/CoffeeMachineTestJs2.java +++ b/src/test/java/projects/javascript/coffee_machine/stage2/test/CoffeeMachineTestJs2.java @@ -3,12 +3,21 @@ import org.hyperskill.hstest.stage.StageTest; import org.hyperskill.hstest.testcase.CheckResult; import org.hyperskill.hstest.testcase.TestCase; +import org.junit.Assume; +import org.junit.BeforeClass; import java.util.List; +import static org.hyperskill.hstest.testing.ExecutionOptions.includeProcessTesting; + public class CoffeeMachineTestJs2 extends StageTest { + @BeforeClass + public static void stopProcessTest() { + Assume.assumeTrue(includeProcessTesting); + } + @Override public List> generate() { return List.of( diff --git a/src/test/java/projects/javascript/coffee_machine/stage3/test/CoffeeMachineTestJs3.java b/src/test/java/projects/javascript/coffee_machine/stage3/test/CoffeeMachineTestJs3.java index fb8a2010..1f5bee91 100644 --- a/src/test/java/projects/javascript/coffee_machine/stage3/test/CoffeeMachineTestJs3.java +++ b/src/test/java/projects/javascript/coffee_machine/stage3/test/CoffeeMachineTestJs3.java @@ -3,9 +3,13 @@ import org.hyperskill.hstest.stage.StageTest; import org.hyperskill.hstest.testcase.CheckResult; import org.hyperskill.hstest.testcase.TestCase; +import org.junit.Assume; +import org.junit.BeforeClass; import java.util.List; +import static org.hyperskill.hstest.testing.ExecutionOptions.includeProcessTesting; + class TestClue { boolean cond; @@ -20,6 +24,11 @@ class TestClue { public class CoffeeMachineTestJs3 extends StageTest { + @BeforeClass + public static void stopProcessTest() { + Assume.assumeTrue(includeProcessTesting); + } + @Override public List> generate() { return List.of( diff --git a/src/test/java/projects/javascript/coffee_machine/stage4/test/CoffeeMachineTestJs4.java b/src/test/java/projects/javascript/coffee_machine/stage4/test/CoffeeMachineTestJs4.java index 879a56af..d8791b1b 100644 --- a/src/test/java/projects/javascript/coffee_machine/stage4/test/CoffeeMachineTestJs4.java +++ b/src/test/java/projects/javascript/coffee_machine/stage4/test/CoffeeMachineTestJs4.java @@ -3,10 +3,14 @@ import org.hyperskill.hstest.stage.StageTest; import org.hyperskill.hstest.testcase.CheckResult; import org.hyperskill.hstest.testcase.TestCase; +import org.junit.Assume; +import org.junit.BeforeClass; import java.util.ArrayList; import java.util.List; +import static org.hyperskill.hstest.testing.ExecutionOptions.includeProcessTesting; + class TestClue { String string; @@ -17,6 +21,11 @@ class TestClue { public class CoffeeMachineTestJs4 extends StageTest { + @BeforeClass + public static void stopProcessTest() { + Assume.assumeTrue(includeProcessTesting); + } + @Override public List> generate() { return List.of( diff --git a/src/test/java/projects/javascript/coffee_machine/stage5/test/CoffeeMachineTestJs5.java b/src/test/java/projects/javascript/coffee_machine/stage5/test/CoffeeMachineTestJs5.java index f4cbbfd5..3d2bee9e 100644 --- a/src/test/java/projects/javascript/coffee_machine/stage5/test/CoffeeMachineTestJs5.java +++ b/src/test/java/projects/javascript/coffee_machine/stage5/test/CoffeeMachineTestJs5.java @@ -3,10 +3,14 @@ import org.hyperskill.hstest.stage.StageTest; import org.hyperskill.hstest.testcase.CheckResult; import org.hyperskill.hstest.testcase.TestCase; +import org.junit.Assume; +import org.junit.BeforeClass; import java.util.ArrayList; import java.util.List; +import static org.hyperskill.hstest.testing.ExecutionOptions.includeProcessTesting; + class TestClue { int water; @@ -27,6 +31,11 @@ class TestClue { public class CoffeeMachineTestJs5 extends StageTest { + @BeforeClass + public static void stopProcessTest() { + Assume.assumeTrue(includeProcessTesting); + } + @Override public List> generate() { return List.of( diff --git a/src/test/java/projects/python/coffee_machine/stage1/test/CoffeeMachineTestPy1.java b/src/test/java/projects/python/coffee_machine/stage1/test/CoffeeMachineTestPy1.java index 9f394324..c2ca3750 100644 --- a/src/test/java/projects/python/coffee_machine/stage1/test/CoffeeMachineTestPy1.java +++ b/src/test/java/projects/python/coffee_machine/stage1/test/CoffeeMachineTestPy1.java @@ -3,12 +3,21 @@ import org.hyperskill.hstest.stage.StageTest; import org.hyperskill.hstest.testcase.CheckResult; import org.hyperskill.hstest.testcase.TestCase; +import org.junit.Assume; +import org.junit.BeforeClass; import java.util.List; +import static org.hyperskill.hstest.testing.ExecutionOptions.includeProcessTesting; + public class CoffeeMachineTestPy1 extends StageTest { + @BeforeClass + public static void stopProcessTest() { + Assume.assumeTrue(includeProcessTesting); + } + @Override public List> generate() { return List.of( diff --git a/src/test/java/projects/python/coffee_machine/stage1_ce/test/CoffeeMachineTestPy1Ce.java b/src/test/java/projects/python/coffee_machine/stage1_ce/test/CoffeeMachineTestPy1Ce.java index cf3c3214..fb03f421 100644 --- a/src/test/java/projects/python/coffee_machine/stage1_ce/test/CoffeeMachineTestPy1Ce.java +++ b/src/test/java/projects/python/coffee_machine/stage1_ce/test/CoffeeMachineTestPy1Ce.java @@ -2,15 +2,24 @@ import org.hyperskill.hstest.testcase.CheckResult; import org.hyperskill.hstest.testcase.TestCase; +import org.junit.Assume; +import org.junit.BeforeClass; import outcomes.base.ContainsMessage; import outcomes.base.NotContainMessage; import outcomes.base.UserErrorTest; import java.util.List; +import static org.hyperskill.hstest.testing.ExecutionOptions.includeProcessTesting; + public class CoffeeMachineTestPy1Ce extends UserErrorTest { + @BeforeClass + public static void stopProcessTest() { + Assume.assumeTrue(includeProcessTesting); + } + @ContainsMessage String msg = "Exception in test #1\n" + "\n" + diff --git a/src/test/java/projects/python/coffee_machine/stage1_ex/test/CoffeeMachineTestPy1Ex.java b/src/test/java/projects/python/coffee_machine/stage1_ex/test/CoffeeMachineTestPy1Ex.java index e6a170a4..a386acea 100644 --- a/src/test/java/projects/python/coffee_machine/stage1_ex/test/CoffeeMachineTestPy1Ex.java +++ b/src/test/java/projects/python/coffee_machine/stage1_ex/test/CoffeeMachineTestPy1Ex.java @@ -2,15 +2,24 @@ import org.hyperskill.hstest.testcase.CheckResult; import org.hyperskill.hstest.testcase.TestCase; +import org.junit.Assume; +import org.junit.BeforeClass; import outcomes.base.ContainsMessage; import outcomes.base.NotContainMessage; import outcomes.base.UserErrorTest; import java.util.List; +import static org.hyperskill.hstest.testing.ExecutionOptions.includeProcessTesting; + public class CoffeeMachineTestPy1Ex extends UserErrorTest { + @BeforeClass + public static void stopProcessTest() { + Assume.assumeTrue(includeProcessTesting); + } + @ContainsMessage String msg = "Exception in test #1\n" + "\n" + diff --git a/src/test/java/projects/python/coffee_machine/stage1_wa/test/CoffeeMachineTestPy1Wa.java b/src/test/java/projects/python/coffee_machine/stage1_wa/test/CoffeeMachineTestPy1Wa.java index 04b19e59..4b7293be 100644 --- a/src/test/java/projects/python/coffee_machine/stage1_wa/test/CoffeeMachineTestPy1Wa.java +++ b/src/test/java/projects/python/coffee_machine/stage1_wa/test/CoffeeMachineTestPy1Wa.java @@ -2,14 +2,23 @@ import org.hyperskill.hstest.testcase.CheckResult; import org.hyperskill.hstest.testcase.TestCase; +import org.junit.Assume; +import org.junit.BeforeClass; import outcomes.base.ContainsMessage; import outcomes.base.UserErrorTest; import java.util.List; +import static org.hyperskill.hstest.testing.ExecutionOptions.includeProcessTesting; + public class CoffeeMachineTestPy1Wa extends UserErrorTest { + @BeforeClass + public static void stopProcessTest() { + Assume.assumeTrue(includeProcessTesting); + } + @ContainsMessage String msg = "Wrong answer in test #1\n" + "\n" + diff --git a/src/test/java/projects/python/coffee_machine/stage2/test/CoffeeMachineTestPy2.java b/src/test/java/projects/python/coffee_machine/stage2/test/CoffeeMachineTestPy2.java index 0730d113..feb20325 100644 --- a/src/test/java/projects/python/coffee_machine/stage2/test/CoffeeMachineTestPy2.java +++ b/src/test/java/projects/python/coffee_machine/stage2/test/CoffeeMachineTestPy2.java @@ -3,12 +3,21 @@ import org.hyperskill.hstest.stage.StageTest; import org.hyperskill.hstest.testcase.CheckResult; import org.hyperskill.hstest.testcase.TestCase; +import org.junit.Assume; +import org.junit.BeforeClass; import java.util.List; +import static org.hyperskill.hstest.testing.ExecutionOptions.includeProcessTesting; + public class CoffeeMachineTestPy2 extends StageTest { + @BeforeClass + public static void stopProcessTest() { + Assume.assumeTrue(includeProcessTesting); + } + @Override public List> generate() { return List.of( diff --git a/src/test/java/projects/python/coffee_machine/stage3/test/CoffeeMachineTestPy3.java b/src/test/java/projects/python/coffee_machine/stage3/test/CoffeeMachineTestPy3.java index 851c75f6..6a95d5e1 100644 --- a/src/test/java/projects/python/coffee_machine/stage3/test/CoffeeMachineTestPy3.java +++ b/src/test/java/projects/python/coffee_machine/stage3/test/CoffeeMachineTestPy3.java @@ -3,9 +3,13 @@ import org.hyperskill.hstest.stage.StageTest; import org.hyperskill.hstest.testcase.CheckResult; import org.hyperskill.hstest.testcase.TestCase; +import org.junit.Assume; +import org.junit.BeforeClass; import java.util.List; +import static org.hyperskill.hstest.testing.ExecutionOptions.includeProcessTesting; + class TestClue { boolean cond; @@ -20,6 +24,11 @@ class TestClue { public class CoffeeMachineTestPy3 extends StageTest { + @BeforeClass + public static void stopProcessTest() { + Assume.assumeTrue(includeProcessTesting); + } + @Override public List> generate() { return List.of( diff --git a/src/test/java/projects/python/coffee_machine/stage4/test/CoffeeMachineTestPy4.java b/src/test/java/projects/python/coffee_machine/stage4/test/CoffeeMachineTestPy4.java index d446e11e..8c16d48a 100644 --- a/src/test/java/projects/python/coffee_machine/stage4/test/CoffeeMachineTestPy4.java +++ b/src/test/java/projects/python/coffee_machine/stage4/test/CoffeeMachineTestPy4.java @@ -3,10 +3,14 @@ import org.hyperskill.hstest.stage.StageTest; import org.hyperskill.hstest.testcase.CheckResult; import org.hyperskill.hstest.testcase.TestCase; +import org.junit.Assume; +import org.junit.BeforeClass; import java.util.ArrayList; import java.util.List; +import static org.hyperskill.hstest.testing.ExecutionOptions.includeProcessTesting; + class TestClue { String string; @@ -17,6 +21,11 @@ class TestClue { public class CoffeeMachineTestPy4 extends StageTest { + @BeforeClass + public static void stopProcessTest() { + Assume.assumeTrue(includeProcessTesting); + } + @Override public List> generate() { return List.of( diff --git a/src/test/java/projects/python/coffee_machine/stage5/test/CoffeeMachineTestPy5.java b/src/test/java/projects/python/coffee_machine/stage5/test/CoffeeMachineTestPy5.java index a52f6795..f5210461 100644 --- a/src/test/java/projects/python/coffee_machine/stage5/test/CoffeeMachineTestPy5.java +++ b/src/test/java/projects/python/coffee_machine/stage5/test/CoffeeMachineTestPy5.java @@ -3,10 +3,14 @@ import org.hyperskill.hstest.stage.StageTest; import org.hyperskill.hstest.testcase.CheckResult; import org.hyperskill.hstest.testcase.TestCase; +import org.junit.Assume; +import org.junit.BeforeClass; import java.util.ArrayList; import java.util.List; +import static org.hyperskill.hstest.testing.ExecutionOptions.includeProcessTesting; + class TestClue { int water; @@ -27,6 +31,11 @@ class TestClue { public class CoffeeMachineTestPy5 extends StageTest { + @BeforeClass + public static void stopProcessTest() { + Assume.assumeTrue(includeProcessTesting); + } + @Override public List> generate() { return List.of( From 43844aab33ab1f565a1635aefdaecc498f29f6e5 Mon Sep 17 00:00:00 2001 From: Vladimir Turov Date: Sat, 20 Nov 2021 01:54:14 +0300 Subject: [PATCH 17/55] Fix reading request --- .../hstest/mocks/web/request/HttpRequestExecutor.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/hyperskill/hstest/mocks/web/request/HttpRequestExecutor.java b/src/main/java/org/hyperskill/hstest/mocks/web/request/HttpRequestExecutor.java index 533db1a8..5b3ba6ab 100644 --- a/src/main/java/org/hyperskill/hstest/mocks/web/request/HttpRequestExecutor.java +++ b/src/main/java/org/hyperskill/hstest/mocks/web/request/HttpRequestExecutor.java @@ -74,18 +74,19 @@ private static HttpResponse executeRequest(HttpRequestBase request, HttpRequest byte[] lastRawPortion = new byte[readBytes]; System.arraycopy(rawPortion, 0, lastRawPortion, 0, readBytes); buffer.add(new BufferPortion(lastRawPortion)); - break; + } else { + buffer.add(new BufferPortion(rawPortion)); } - buffer.add(new BufferPortion(rawPortion)); } byte[] rawContent = new byte[contentLength]; - for (int i = 0; i < buffer.size(); i++) { - BufferPortion portion = buffer.get(i); + int currentDest = 0; + for (BufferPortion portion : buffer) { System.arraycopy( portion.buffer, 0, - rawContent, i * READ_CHUNK, + rawContent, currentDest, portion.buffer.length); + currentDest += portion.buffer.length; } request.releaseConnection(); From 37c5c7e8e0ac6a706015d3d27b87cf9724d2ec3c Mon Sep 17 00:00:00 2001 From: Vladimir Turov Date: Sat, 20 Nov 2021 02:09:29 +0300 Subject: [PATCH 18/55] Make jitpack build with Java 11 --- jitpack.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 jitpack.yml diff --git a/jitpack.yml b/jitpack.yml new file mode 100644 index 00000000..f78f664d --- /dev/null +++ b/jitpack.yml @@ -0,0 +1 @@ +jdk: openjdk11 \ No newline at end of file From 53dba0b9ef850c505a0a7881dd251f2824de0648 Mon Sep 17 00:00:00 2001 From: Vladimir Turov Date: Sat, 20 Nov 2021 02:26:58 +0300 Subject: [PATCH 19/55] Add token auth into HttpRequest --- .../org/hyperskill/hstest/mocks/web/request/HttpRequest.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/org/hyperskill/hstest/mocks/web/request/HttpRequest.java b/src/main/java/org/hyperskill/hstest/mocks/web/request/HttpRequest.java index a9839cd9..762f6b14 100644 --- a/src/main/java/org/hyperskill/hstest/mocks/web/request/HttpRequest.java +++ b/src/main/java/org/hyperskill/hstest/mocks/web/request/HttpRequest.java @@ -256,6 +256,11 @@ public HttpRequest basicAuth(String login, String pass) { return addHeader(AUTHORIZATION, headerValue); } + public HttpRequest tokenAuth(String token) { + String headerValue = "Bearer " + token; + return addHeader(AUTHORIZATION, headerValue); + } + public HttpResponse send() { if (method.equals(POST.toString()) && !params.isEmpty()) { content = packUrlParams(params); From 661aa4c55fd6b9d3b9536e15fa287117293b2689 Mon Sep 17 00:00:00 2001 From: Vladimir Turov Date: Wed, 26 Jan 2022 19:52:41 +0300 Subject: [PATCH 20/55] Further simplify searching code --- .../execution/searcher/BaseSearcher.java | 17 ++++++++++++++++ .../execution/searcher/GoSearcher.java | 20 +++---------------- .../searcher/JavascriptSearcher.java | 20 +++---------------- 3 files changed, 23 insertions(+), 34 deletions(-) diff --git a/src/main/java/org/hyperskill/hstest/testing/execution/searcher/BaseSearcher.java b/src/main/java/org/hyperskill/hstest/testing/execution/searcher/BaseSearcher.java index cf20b218..ed077582 100644 --- a/src/main/java/org/hyperskill/hstest/testing/execution/searcher/BaseSearcher.java +++ b/src/main/java/org/hyperskill/hstest/testing/execution/searcher/BaseSearcher.java @@ -14,7 +14,9 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.regex.Pattern; +import static java.util.regex.Pattern.MULTILINE; import static java.util.stream.Collectors.toSet; public abstract class BaseSearcher { @@ -199,6 +201,21 @@ protected RunnableFile searchCached( return result; } + protected RunnableFile simpleSearch(String whereToSearch, String mainDesc, String mainRegex) { + var mainSearcher = Pattern.compile(mainRegex, MULTILINE); + + var mainFilter = new MainFilter(mainDesc); + mainFilter.source(FileFilter.regexFilter(mainSearcher)); + + return searchCached( + whereToSearch, + null, + null, + mainFilter, + null + ); + } + @Data private static class ParsedSource { final String folder; diff --git a/src/main/java/org/hyperskill/hstest/testing/execution/searcher/GoSearcher.java b/src/main/java/org/hyperskill/hstest/testing/execution/searcher/GoSearcher.java index a055f73b..1e73b07a 100644 --- a/src/main/java/org/hyperskill/hstest/testing/execution/searcher/GoSearcher.java +++ b/src/main/java/org/hyperskill/hstest/testing/execution/searcher/GoSearcher.java @@ -1,13 +1,7 @@ package org.hyperskill.hstest.testing.execution.searcher; -import org.hyperskill.hstest.testing.execution.filtering.FileFilter; -import org.hyperskill.hstest.testing.execution.filtering.MainFilter; import org.hyperskill.hstest.testing.execution.runnable.RunnableFile; -import java.util.regex.Pattern; - -import static java.util.regex.Pattern.MULTILINE; - public class GoSearcher extends BaseSearcher { @Override public String extension() { @@ -16,17 +10,9 @@ public String extension() { @Override public RunnableFile search(String whereToSearch) { - var mainSearcher = Pattern.compile("(^|\n) *func +main +\\( *\\)", MULTILINE); - - var mainFilter = new MainFilter("func main()"); - mainFilter.source(FileFilter.regexFilter(mainSearcher)); - - return searchCached( - whereToSearch, - null, - null, - mainFilter, - null + return simpleSearch(whereToSearch, + "func main()", + "(^|\n) *func +main +\\( *\\)" ); } } diff --git a/src/main/java/org/hyperskill/hstest/testing/execution/searcher/JavascriptSearcher.java b/src/main/java/org/hyperskill/hstest/testing/execution/searcher/JavascriptSearcher.java index 1c69a0b4..6764fb5a 100644 --- a/src/main/java/org/hyperskill/hstest/testing/execution/searcher/JavascriptSearcher.java +++ b/src/main/java/org/hyperskill/hstest/testing/execution/searcher/JavascriptSearcher.java @@ -1,13 +1,7 @@ package org.hyperskill.hstest.testing.execution.searcher; -import org.hyperskill.hstest.testing.execution.filtering.FileFilter; -import org.hyperskill.hstest.testing.execution.filtering.MainFilter; import org.hyperskill.hstest.testing.execution.runnable.RunnableFile; -import java.util.regex.Pattern; - -import static java.util.regex.Pattern.MULTILINE; - public class JavascriptSearcher extends BaseSearcher { @Override public String extension() { @@ -16,17 +10,9 @@ public String extension() { @Override public RunnableFile search(String whereToSearch) { - var mainSearcher = Pattern.compile("(^|\n) *function +main +\\( *\\)", MULTILINE); - - var mainFilter = new MainFilter("function main()"); - mainFilter.source(FileFilter.regexFilter(mainSearcher)); - - return searchCached( - whereToSearch, - null, - null, - mainFilter, - null + return simpleSearch(whereToSearch, + "function main()", + "(^|\n) *function +main +\\( *\\)" ); } } From 0f0b5b439c622c5030d3113972c816cb4a85dbe1 Mon Sep 17 00:00:00 2001 From: Vladimir Turov Date: Wed, 26 Jan 2022 20:03:46 +0300 Subject: [PATCH 21/55] Fix running on Windows --- .../java/org/hyperskill/hstest/testing/ProcessWrapper.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/hyperskill/hstest/testing/ProcessWrapper.java b/src/main/java/org/hyperskill/hstest/testing/ProcessWrapper.java index 4d626f51..cf7c83d1 100644 --- a/src/main/java/org/hyperskill/hstest/testing/ProcessWrapper.java +++ b/src/main/java/org/hyperskill/hstest/testing/ProcessWrapper.java @@ -94,8 +94,10 @@ public ProcessWrapper start() { if (OsUtils.isWindows()) { // To test this in windows you need WSL2 installed - fullArgs.add("bash"); - fullArgs.add("-c"); + // fullArgs.add("bash"); + // fullArgs.add("-c"); + fullArgs.add("cmd"); + fullArgs.add("/c"); } fullArgs.addAll(List.of(args)); From 4f623e0a7357eb567e59f5877f99d5e19f5510db Mon Sep 17 00:00:00 2001 From: Vladimir Turov Date: Thu, 27 Jan 2022 20:24:02 +0300 Subject: [PATCH 22/55] Support testing bash --- .../org/hyperskill/hstest/common/Utils.java | 15 +++ .../dynamic/input/DynamicInputHandler.java | 2 +- .../hyperskill/hstest/stage/StageTest.java | 4 + .../hstest/testing/ProcessWrapper.java | 13 ++- .../hyperskill/hstest/testing/TestRun.java | 4 + .../execution/process/ShellExecutor.java | 24 ++++ .../execution/searcher/ShellSearcher.java | 18 +++ .../shell/coffee_machine/stage1/main.sh | 7 ++ .../stage1/test/CoffeeMachineTestShell1.java | 43 +++++++ .../shell/coffee_machine/stage1_ex/main.sh | 1 + .../test/CoffeeMachineTestShell1Ex.java | 49 ++++++++ .../shell/coffee_machine/stage1_wa/main.sh | 1 + .../test/CoffeeMachineTestShell1Wa.java | 51 +++++++++ .../shell/coffee_machine/stage2/main.sh | 7 ++ .../stage2/test/CoffeeMachineTestShell2.java | 107 ++++++++++++++++++ 15 files changed, 341 insertions(+), 5 deletions(-) create mode 100644 src/main/java/org/hyperskill/hstest/testing/execution/process/ShellExecutor.java create mode 100644 src/main/java/org/hyperskill/hstest/testing/execution/searcher/ShellSearcher.java create mode 100644 src/test/java/projects/shell/coffee_machine/stage1/main.sh create mode 100644 src/test/java/projects/shell/coffee_machine/stage1/test/CoffeeMachineTestShell1.java create mode 100644 src/test/java/projects/shell/coffee_machine/stage1_ex/main.sh create mode 100644 src/test/java/projects/shell/coffee_machine/stage1_ex/test/CoffeeMachineTestShell1Ex.java create mode 100644 src/test/java/projects/shell/coffee_machine/stage1_wa/main.sh create mode 100644 src/test/java/projects/shell/coffee_machine/stage1_wa/test/CoffeeMachineTestShell1Wa.java create mode 100644 src/test/java/projects/shell/coffee_machine/stage2/main.sh create mode 100644 src/test/java/projects/shell/coffee_machine/stage2/test/CoffeeMachineTestShell2.java diff --git a/src/main/java/org/hyperskill/hstest/common/Utils.java b/src/main/java/org/hyperskill/hstest/common/Utils.java index 50f8991d..b7284584 100644 --- a/src/main/java/org/hyperskill/hstest/common/Utils.java +++ b/src/main/java/org/hyperskill/hstest/common/Utils.java @@ -78,6 +78,21 @@ public static String capitalize(String str) { + (str.length() > 1 ? str.substring(1) : ""); } + /** + * Escapes the string, useful for debugging + * Example: "123\n456" -> "123\\n456" + */ + public static String escape(String str) { + return str.replace("\\", "\\\\") + .replace("\t", "\\t") + .replace("\b", "\\b") + .replace("\n", "\\n") + .replace("\r", "\\r") + .replace("\f", "\\f") + .replace("'", "\\'") + .replace("\"", "\\\""); + } + /** * Numbers-aware compare. * Compare strings as strings, but numbers inside strings as numbers. diff --git a/src/main/java/org/hyperskill/hstest/dynamic/input/DynamicInputHandler.java b/src/main/java/org/hyperskill/hstest/dynamic/input/DynamicInputHandler.java index 84b73774..4190dc33 100644 --- a/src/main/java/org/hyperskill/hstest/dynamic/input/DynamicInputHandler.java +++ b/src/main/java/org/hyperskill/hstest/dynamic/input/DynamicInputHandler.java @@ -3,7 +3,7 @@ import org.hyperskill.hstest.dynamic.output.InfiniteLoopDetector; import org.hyperskill.hstest.dynamic.output.OutputHandler; import org.hyperskill.hstest.dynamic.security.ExitException; -import org.hyperskill.hstest.exception.outcomes.ErrorWithFeedback; +import org.hyperskill.hstest.exception.outcomes.OutOfInputError; import org.hyperskill.hstest.stage.StageTest; import org.hyperskill.hstest.testing.Settings; diff --git a/src/main/java/org/hyperskill/hstest/stage/StageTest.java b/src/main/java/org/hyperskill/hstest/stage/StageTest.java index e2686794..1aec623f 100644 --- a/src/main/java/org/hyperskill/hstest/stage/StageTest.java +++ b/src/main/java/org/hyperskill/hstest/stage/StageTest.java @@ -16,6 +16,7 @@ import org.hyperskill.hstest.testing.execution.process.GoExecutor; import org.hyperskill.hstest.testing.execution.process.JavascriptExecutor; import org.hyperskill.hstest.testing.execution.process.PythonExecutor; +import org.hyperskill.hstest.testing.execution.process.ShellExecutor; import org.hyperskill.hstest.testing.runner.AsyncDynamicTestingRunner; import org.hyperskill.hstest.testing.runner.TestRunner; import org.junit.Test; @@ -86,6 +87,9 @@ private TestRunner initRunner() { if (file.getName().endsWith(".py")) { return new AsyncDynamicTestingRunner(PythonExecutor.class); } + if (file.getName().endsWith(".sh")) { + return new AsyncDynamicTestingRunner(ShellExecutor.class); + } } } diff --git a/src/main/java/org/hyperskill/hstest/testing/ProcessWrapper.java b/src/main/java/org/hyperskill/hstest/testing/ProcessWrapper.java index cf7c83d1..30d4af78 100644 --- a/src/main/java/org/hyperskill/hstest/testing/ProcessWrapper.java +++ b/src/main/java/org/hyperskill/hstest/testing/ProcessWrapper.java @@ -194,7 +194,7 @@ private void checkPipe(InputStream readPipe, } } - private void checkStdout() { + private void checkStdout() { checkPipe(process.getInputStream(), System.out, stdout); } @@ -217,9 +217,11 @@ private void checkCpuLoad() { long currCpuLoad = currCpuTime - oldCpuTime; oldCpuTime = currCpuTime; - cpuLoadHistory.add(currCpuLoad); + if (!initialIdleWait) { + cpuLoadHistory.add(currCpuLoad); + } - if (!initialIdleWait && cpuLoadHistory.size() > cpuLoadHistoryMax) { + if (cpuLoadHistory.size() > cpuLoadHistoryMax) { cpuLoadHistory.remove(); } @@ -236,7 +238,10 @@ private void checkOutput() { int diff = currOutputSize - oldOutputSize; oldOutputSize = currOutputSize; - outputDiffHistory.add(diff); + if (!initialIdleWait) { + outputDiffHistory.add(diff); + } + if (outputDiffHistory.size() > outputDiffHistoryMax) { outputDiffHistory.remove(); } diff --git a/src/main/java/org/hyperskill/hstest/testing/TestRun.java b/src/main/java/org/hyperskill/hstest/testing/TestRun.java index 86490eac..7416b81c 100644 --- a/src/main/java/org/hyperskill/hstest/testing/TestRun.java +++ b/src/main/java/org/hyperskill/hstest/testing/TestRun.java @@ -53,6 +53,10 @@ public void setErrorInTest(Throwable errorInTest) { } } + public void resetErrorInTest() { + this.errorInTest = null; + } + public void setInputUsed() { this.inputUsed = true; } diff --git a/src/main/java/org/hyperskill/hstest/testing/execution/process/ShellExecutor.java b/src/main/java/org/hyperskill/hstest/testing/execution/process/ShellExecutor.java new file mode 100644 index 00000000..994ef732 --- /dev/null +++ b/src/main/java/org/hyperskill/hstest/testing/execution/process/ShellExecutor.java @@ -0,0 +1,24 @@ +package org.hyperskill.hstest.testing.execution.process; + +import org.hyperskill.hstest.testing.execution.ProcessExecutor; +import org.hyperskill.hstest.testing.execution.searcher.ShellSearcher; + +import java.util.ArrayList; +import java.util.List; + +public class ShellExecutor extends ProcessExecutor { + + public ShellExecutor(String sourceName) { + super(new ShellSearcher().find(sourceName)); + } + + @Override + protected List executionCommand(List args) { + List fullArgs = new ArrayList<>(); + fullArgs.addAll(List.of("bash", runnable.getFile().getName())); + fullArgs.addAll(args); + + return fullArgs; + } + +} diff --git a/src/main/java/org/hyperskill/hstest/testing/execution/searcher/ShellSearcher.java b/src/main/java/org/hyperskill/hstest/testing/execution/searcher/ShellSearcher.java new file mode 100644 index 00000000..3496a6e8 --- /dev/null +++ b/src/main/java/org/hyperskill/hstest/testing/execution/searcher/ShellSearcher.java @@ -0,0 +1,18 @@ +package org.hyperskill.hstest.testing.execution.searcher; + +import org.hyperskill.hstest.testing.execution.runnable.RunnableFile; + +public class ShellSearcher extends BaseSearcher { + @Override + public String extension() { + return ".sh"; + } + + @Override + public RunnableFile search(String whereToSearch) { + return simpleSearch(whereToSearch, + "# main", + "(^|\n)# *main" + ); + } +} diff --git a/src/test/java/projects/shell/coffee_machine/stage1/main.sh b/src/test/java/projects/shell/coffee_machine/stage1/main.sh new file mode 100644 index 00000000..80a9e955 --- /dev/null +++ b/src/test/java/projects/shell/coffee_machine/stage1/main.sh @@ -0,0 +1,7 @@ +echo Starting to make a coffee +echo Grinding coffee beans +echo Boiling water +echo Mixing boiled water with crushed coffee beans +echo Pouring coffee into the cup +echo Pouring some milk into the cup +echo Coffee is ready! \ No newline at end of file diff --git a/src/test/java/projects/shell/coffee_machine/stage1/test/CoffeeMachineTestShell1.java b/src/test/java/projects/shell/coffee_machine/stage1/test/CoffeeMachineTestShell1.java new file mode 100644 index 00000000..1217aad3 --- /dev/null +++ b/src/test/java/projects/shell/coffee_machine/stage1/test/CoffeeMachineTestShell1.java @@ -0,0 +1,43 @@ +package projects.shell.coffee_machine.stage1.test; + +import org.hyperskill.hstest.stage.StageTest; +import org.hyperskill.hstest.testcase.CheckResult; +import org.hyperskill.hstest.testcase.TestCase; +import org.junit.Assume; +import org.junit.BeforeClass; + +import java.util.List; + +import static org.hyperskill.hstest.testing.ExecutionOptions.includeProcessTesting; + + +public class CoffeeMachineTestShell1 extends StageTest { + + @BeforeClass + public static void stopProcessTest() { + Assume.assumeTrue(includeProcessTesting); + } + + @Override + public List> generate() { + return List.of( + new TestCase() + .setInput("") + .setAttach("Starting to make a coffee\n" + + "Grinding coffee beans\n" + + "Boiling water\n" + + "Mixing boiled water with crushed coffee beans\n" + + "Pouring coffee into the cup\n" + + "Pouring some milk into the cup\n" + + "Coffee is ready!") + ); + } + + @Override + public CheckResult check(String reply, String clue) { + boolean isCorrect = reply.trim().equals(clue.trim()); + return new CheckResult(isCorrect, + "You should make coffee exactly " + + "like in the example"); + } +} diff --git a/src/test/java/projects/shell/coffee_machine/stage1_ex/main.sh b/src/test/java/projects/shell/coffee_machine/stage1_ex/main.sh new file mode 100644 index 00000000..a481e0a1 --- /dev/null +++ b/src/test/java/projects/shell/coffee_machine/stage1_ex/main.sh @@ -0,0 +1 @@ +ech \ No newline at end of file diff --git a/src/test/java/projects/shell/coffee_machine/stage1_ex/test/CoffeeMachineTestShell1Ex.java b/src/test/java/projects/shell/coffee_machine/stage1_ex/test/CoffeeMachineTestShell1Ex.java new file mode 100644 index 00000000..e91c0fd7 --- /dev/null +++ b/src/test/java/projects/shell/coffee_machine/stage1_ex/test/CoffeeMachineTestShell1Ex.java @@ -0,0 +1,49 @@ +package projects.shell.coffee_machine.stage1_ex.test; + +import org.hyperskill.hstest.testcase.CheckResult; +import org.hyperskill.hstest.testcase.TestCase; +import org.junit.Assume; +import org.junit.BeforeClass; +import outcomes.base.ContainsMessage; +import outcomes.base.UserErrorTest; + +import java.util.List; + +import static org.hyperskill.hstest.testing.ExecutionOptions.includeProcessTesting; + + +public class CoffeeMachineTestShell1Ex extends UserErrorTest { + + @BeforeClass + public static void stopProcessTest() { + Assume.assumeTrue(includeProcessTesting); + } + + @ContainsMessage + String msg = "Exception in test #1\n" + + "\n" + + "main.sh: line 1: ech: command not found"; + + @Override + public List> generate() { + return List.of( + new TestCase() + .setInput("") + .setAttach("Starting to make a coffee\n" + + "Grinding coffee beans\n" + + "Boiling water\n" + + "Mixing boiled water with crushed coffee beans\n" + + "Pouring coffee into the cup\n" + + "Pouring some milk into the cup\n" + + "Coffee is ready!") + ); + } + + @Override + public CheckResult check(String reply, String clue) { + boolean isCorrect = reply.trim().equals(clue.trim()); + return new CheckResult(isCorrect, + "You should make coffee exactly " + + "like in the example"); + } +} diff --git a/src/test/java/projects/shell/coffee_machine/stage1_wa/main.sh b/src/test/java/projects/shell/coffee_machine/stage1_wa/main.sh new file mode 100644 index 00000000..3b701b6d --- /dev/null +++ b/src/test/java/projects/shell/coffee_machine/stage1_wa/main.sh @@ -0,0 +1 @@ +echo 12123123 diff --git a/src/test/java/projects/shell/coffee_machine/stage1_wa/test/CoffeeMachineTestShell1Wa.java b/src/test/java/projects/shell/coffee_machine/stage1_wa/test/CoffeeMachineTestShell1Wa.java new file mode 100644 index 00000000..64ba5a2b --- /dev/null +++ b/src/test/java/projects/shell/coffee_machine/stage1_wa/test/CoffeeMachineTestShell1Wa.java @@ -0,0 +1,51 @@ +package projects.shell.coffee_machine.stage1_wa.test; + +import org.hyperskill.hstest.testcase.CheckResult; +import org.hyperskill.hstest.testcase.TestCase; +import org.junit.BeforeClass; +import outcomes.base.ContainsMessage; +import outcomes.base.UserErrorTest; + +import java.util.List; + + +public class CoffeeMachineTestShell1Wa extends UserErrorTest { + + @BeforeClass + public static void stopProcessTest() { + //Assume.assumeTrue(includeProcessTesting); + } + + @ContainsMessage + String msg = "Wrong answer in test #1\n" + + "\n" + + "You should make coffee exactly like in the example\n" + + "\n" + + "Please find below the output of your program during this failed test.\n" + + "\n" + + "---\n" + + "\n" + + "12123123"; + + @Override + public List> generate() { + return List.of( + new TestCase() + .setAttach("Starting to make a coffee\n" + + "Grinding coffee beans\n" + + "Boiling water\n" + + "Mixing boiled water with crushed coffee beans\n" + + "Pouring coffee into the cup\n" + + "Pouring some milk into the cup\n" + + "Coffee is ready!") + ); + } + + @Override + public CheckResult check(String reply, String clue) { + boolean isCorrect = reply.trim().equals(clue.trim()); + return new CheckResult(isCorrect, + "You should make coffee exactly " + + "like in the example"); + } +} diff --git a/src/test/java/projects/shell/coffee_machine/stage2/main.sh b/src/test/java/projects/shell/coffee_machine/stage2/main.sh new file mode 100644 index 00000000..cfeb134f --- /dev/null +++ b/src/test/java/projects/shell/coffee_machine/stage2/main.sh @@ -0,0 +1,7 @@ +echo Write how many cups of coffee you will need: +read -r cups +printf "\nYou entered: \"%q\"\n" "$cups" +echo "For $cups cups of coffee you will need:" +echo "$((cups * 200)) ml of water" +echo "$((cups * 50)) ml of milk" +echo "$((cups * 15)) g of coffee beans" \ No newline at end of file diff --git a/src/test/java/projects/shell/coffee_machine/stage2/test/CoffeeMachineTestShell2.java b/src/test/java/projects/shell/coffee_machine/stage2/test/CoffeeMachineTestShell2.java new file mode 100644 index 00000000..a63a461f --- /dev/null +++ b/src/test/java/projects/shell/coffee_machine/stage2/test/CoffeeMachineTestShell2.java @@ -0,0 +1,107 @@ +package projects.shell.coffee_machine.stage2.test; + +import org.hyperskill.hstest.stage.StageTest; +import org.hyperskill.hstest.testcase.CheckResult; +import org.hyperskill.hstest.testcase.TestCase; +import org.junit.BeforeClass; + +import java.util.List; + + +public class CoffeeMachineTestShell2 extends StageTest { + + @BeforeClass + public static void stopProcessTest() { + //Assume.assumeTrue(includeProcessTesting); + } + + @Override + public List> generate() { + return List.of( + new TestCase() + .setInput("25") + .setAttach("25"), + + new TestCase() + .setInput("125") + .setAttach("125"), + + new TestCase() + .setInput("1") + .setAttach("1"), + + new TestCase() + .setInput("123") + .setAttach("123") + ); + } + + @Override + public CheckResult check(String reply, String clue) { + String[] lines = reply.split("\\n"); + if (lines.length < 3) { + return new CheckResult(false, + "Output contains less than 3 lines, but should output at least 3 lines"); + } + String[] last3Lines = { + lines[lines.length - 3], + lines[lines.length - 2], + lines[lines.length - 1] + }; + + int cups = Integer.parseInt(clue); + boolean water = false, milk = false, beans = false; + + for(String line : last3Lines) { + line = line.toLowerCase(); + + if(line.contains("milk")) { + milk = line.contains(Integer.toString(cups * 50)); + if (!milk) { + return new CheckResult(false, + "For the input " + clue + " your program output:\n\"" + + line + "\"\nbut the amount of milk should be " + (cups * 50)); + } + + } else if(line.contains("water")) { + water = line.contains(Integer.toString(cups * 200)); + if (!water) { + return new CheckResult(false, + "For the input " + clue + " your program output:\n" + + line + "\nbut the amount of water should be " + (cups * 200)); + } + + } else if(line.contains("beans")) { + beans = line.contains(Integer.toString(cups * 15)); + if (!beans) { + return new CheckResult(false, + "For the input " + clue + " your program output:\n" + + line + "\nbut the amount of beans should be " + (cups * 15)); + } + + + } else { + return new CheckResult(false, + "One of the last 3 lines " + + "doesn't contain \"milk\", \"water\" or \"beans\""); + } + } + + if (!water) { + return new CheckResult(false, + "There is no line with amount of water"); + } + + if (!milk) { + return new CheckResult(false, + "There is no line with amount of milk"); + } + + if (!beans) { + return new CheckResult(false, + "There is no line with amount of coffee beans"); + } + + return CheckResult.correct(); + } +} From e04e0e1a81abaff442b04005745a9c0ffd15e7aa Mon Sep 17 00:00:00 2001 From: Vladimir Turov Date: Thu, 27 Jan 2022 20:43:24 +0300 Subject: [PATCH 23/55] Check if process really requests input or just shuts down really slow --- .../hstest/dynamic/input/DynamicInputHandler.java | 3 +-- .../hstest/exception/outcomes/OutOfInputError.java | 7 +++++++ .../java/org/hyperskill/hstest/testing/TestRun.java | 6 +----- .../hstest/testing/execution/ProcessExecutor.java | 13 +++++++++++++ 4 files changed, 22 insertions(+), 7 deletions(-) create mode 100644 src/main/java/org/hyperskill/hstest/exception/outcomes/OutOfInputError.java diff --git a/src/main/java/org/hyperskill/hstest/dynamic/input/DynamicInputHandler.java b/src/main/java/org/hyperskill/hstest/dynamic/input/DynamicInputHandler.java index 4190dc33..f2b43485 100644 --- a/src/main/java/org/hyperskill/hstest/dynamic/input/DynamicInputHandler.java +++ b/src/main/java/org/hyperskill/hstest/dynamic/input/DynamicInputHandler.java @@ -30,8 +30,7 @@ int ejectChar() { character = nextByte(); } if (character == -1 && !Settings.allowOutOfInput) { - StageTest.getCurrTestRun().setErrorInTest(new ErrorWithFeedback( - "Program ran out of input. You tried to read more than expected.")); + StageTest.getCurrTestRun().setErrorInTest(new OutOfInputError()); throw new ExitException(0); } return character; diff --git a/src/main/java/org/hyperskill/hstest/exception/outcomes/OutOfInputError.java b/src/main/java/org/hyperskill/hstest/exception/outcomes/OutOfInputError.java new file mode 100644 index 00000000..ed8e00ce --- /dev/null +++ b/src/main/java/org/hyperskill/hstest/exception/outcomes/OutOfInputError.java @@ -0,0 +1,7 @@ +package org.hyperskill.hstest.exception.outcomes; + +public class OutOfInputError extends ErrorWithFeedback { + public OutOfInputError() { + super("Program ran out of input. You tried to read more than expected."); + } +} diff --git a/src/main/java/org/hyperskill/hstest/testing/TestRun.java b/src/main/java/org/hyperskill/hstest/testing/TestRun.java index 7416b81c..2100b43d 100644 --- a/src/main/java/org/hyperskill/hstest/testing/TestRun.java +++ b/src/main/java/org/hyperskill/hstest/testing/TestRun.java @@ -48,15 +48,11 @@ public final boolean isLastTest() { } public void setErrorInTest(Throwable errorInTest) { - if (this.errorInTest == null) { + if (this.errorInTest == null || errorInTest == null) { this.errorInTest = errorInTest; } } - public void resetErrorInTest() { - this.errorInTest = null; - } - public void setInputUsed() { this.inputUsed = true; } diff --git a/src/main/java/org/hyperskill/hstest/testing/execution/ProcessExecutor.java b/src/main/java/org/hyperskill/hstest/testing/execution/ProcessExecutor.java index ed0552f6..58365b53 100644 --- a/src/main/java/org/hyperskill/hstest/testing/execution/ProcessExecutor.java +++ b/src/main/java/org/hyperskill/hstest/testing/execution/ProcessExecutor.java @@ -7,6 +7,7 @@ import org.hyperskill.hstest.dynamic.security.TestingSecurityManager; import org.hyperskill.hstest.exception.outcomes.CompilationError; import org.hyperskill.hstest.exception.outcomes.ExceptionWithFeedback; +import org.hyperskill.hstest.exception.outcomes.OutOfInputError; import org.hyperskill.hstest.stage.StageTest; import org.hyperskill.hstest.testing.ProcessWrapper; import org.hyperskill.hstest.testing.execution.runnable.RunnableFile; @@ -14,6 +15,7 @@ import java.util.List; import static org.hyperskill.hstest.common.Utils.sleep; +import static org.hyperskill.hstest.common.Utils.tryManyTimes; import static org.hyperskill.hstest.testing.execution.ProgramExecutor.ProgramState.COMPILATION_ERROR; import static org.hyperskill.hstest.testing.execution.ProgramExecutor.ProgramState.EXCEPTION_THROWN; import static org.hyperskill.hstest.testing.execution.ProgramExecutor.ProgramState.FINISHED; @@ -109,6 +111,12 @@ private void handleProcess(List args) { var nextInput = InputHandler.readline(); process.provideInput(nextInput); } catch (ExitException ex) { + if (waitIfTerminated()) { + if (StageTest.getCurrTestRun().getErrorInTest() instanceof OutOfInputError) { + StageTest.getCurrTestRun().setErrorInTest(null); + } + break; + } stopInput(); } } @@ -135,6 +143,11 @@ private void handleProcess(List args) { } } + private boolean waitIfTerminated() { + return tryManyTimes(100, 10, + () -> process.isFinished(false)); + } + @Override protected final void launch(String... args) { group = new ThreadGroup(this.toString()); From ec2569d44d9f8066c75664a101be4a0d6c43931a Mon Sep 17 00:00:00 2001 From: Vladimir Turov Date: Thu, 27 Jan 2022 21:42:53 +0300 Subject: [PATCH 24/55] Prepare for Java 18 --- .../org/hyperskill/hstest/common/JavaUtils.java | 16 +++++++++++++--- .../hstest/testing/ExecutionOptions.java | 7 ------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/hyperskill/hstest/common/JavaUtils.java b/src/main/java/org/hyperskill/hstest/common/JavaUtils.java index 43aea375..d58c515c 100644 --- a/src/main/java/org/hyperskill/hstest/common/JavaUtils.java +++ b/src/main/java/org/hyperskill/hstest/common/JavaUtils.java @@ -2,8 +2,6 @@ import java.lang.management.ManagementFactory; -import static org.hyperskill.hstest.testing.ExecutionOptions.forceSecurityManager; - public class JavaUtils { private JavaUtils() { } @@ -15,6 +13,11 @@ private JavaUtils() { } public static int getJavaVersion() { String version = System.getProperty("java.version"); + String earlyAccess = "-ea"; + if (version.endsWith(earlyAccess)) { + version = version.substring(0, version.length() - earlyAccess.length()); + } + if (version.startsWith("1.")) { version = version.substring(2, 3); } else { @@ -32,8 +35,15 @@ public static boolean isUnderDebugger() { .getInputArguments().toString().contains("jdwp"); } + /** + * SecurityManager is not recommended being used in Java 17 but is allowed + * Starting from Java 18 it's prohibited to use + * + * SecurityManager was used to catch System.exit() in user's code + * tp prevent JVM from shutting down + */ public static boolean isSecurityManagerAllowed() { - return getJavaVersion() <= 17 || forceSecurityManager; + return getJavaVersion() <= 17; } /** diff --git a/src/main/java/org/hyperskill/hstest/testing/ExecutionOptions.java b/src/main/java/org/hyperskill/hstest/testing/ExecutionOptions.java index a595c499..991ba264 100644 --- a/src/main/java/org/hyperskill/hstest/testing/ExecutionOptions.java +++ b/src/main/java/org/hyperskill/hstest/testing/ExecutionOptions.java @@ -43,13 +43,6 @@ private ExecutionOptions() { } */ public static boolean debugMode = Boolean.getBoolean("debugMode") || isUnderDebugger(); - /** - * Enables SecurityManager even though it's not recommended being used in Java 17 - * In Java 8-17 it still will be used, on Java 18 it's not unless this flag is set to true. - * Use "-DforceSecurityManager=true" to set this flag. - */ - public static boolean forceSecurityManager = Boolean.getBoolean("forceSecurityManager"); - /** * Enables tests that use process testing to test solutions written in Go, JS etc. * By default, it's turned off because some computers don't have Go, JS etc. installed. From 75b6e0977bce65a9e2ae7552ba1fe622326c29d7 Mon Sep 17 00:00:00 2001 From: Vladimir Turov Date: Fri, 28 Jan 2022 16:14:30 +0300 Subject: [PATCH 25/55] Fix tests --- .../stage1_wa/test/CoffeeMachineTestShell1Wa.java | 5 ++++- src/test/java/projects/shell/coffee_machine/stage2/main.sh | 1 - .../coffee_machine/stage2/test/CoffeeMachineTestShell2.java | 5 ++++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/test/java/projects/shell/coffee_machine/stage1_wa/test/CoffeeMachineTestShell1Wa.java b/src/test/java/projects/shell/coffee_machine/stage1_wa/test/CoffeeMachineTestShell1Wa.java index 64ba5a2b..419c8650 100644 --- a/src/test/java/projects/shell/coffee_machine/stage1_wa/test/CoffeeMachineTestShell1Wa.java +++ b/src/test/java/projects/shell/coffee_machine/stage1_wa/test/CoffeeMachineTestShell1Wa.java @@ -2,18 +2,21 @@ import org.hyperskill.hstest.testcase.CheckResult; import org.hyperskill.hstest.testcase.TestCase; +import org.junit.Assume; import org.junit.BeforeClass; import outcomes.base.ContainsMessage; import outcomes.base.UserErrorTest; import java.util.List; +import static org.hyperskill.hstest.testing.ExecutionOptions.includeProcessTesting; + public class CoffeeMachineTestShell1Wa extends UserErrorTest { @BeforeClass public static void stopProcessTest() { - //Assume.assumeTrue(includeProcessTesting); + Assume.assumeTrue(includeProcessTesting); } @ContainsMessage diff --git a/src/test/java/projects/shell/coffee_machine/stage2/main.sh b/src/test/java/projects/shell/coffee_machine/stage2/main.sh index cfeb134f..43c75694 100644 --- a/src/test/java/projects/shell/coffee_machine/stage2/main.sh +++ b/src/test/java/projects/shell/coffee_machine/stage2/main.sh @@ -1,6 +1,5 @@ echo Write how many cups of coffee you will need: read -r cups -printf "\nYou entered: \"%q\"\n" "$cups" echo "For $cups cups of coffee you will need:" echo "$((cups * 200)) ml of water" echo "$((cups * 50)) ml of milk" diff --git a/src/test/java/projects/shell/coffee_machine/stage2/test/CoffeeMachineTestShell2.java b/src/test/java/projects/shell/coffee_machine/stage2/test/CoffeeMachineTestShell2.java index a63a461f..ba14a899 100644 --- a/src/test/java/projects/shell/coffee_machine/stage2/test/CoffeeMachineTestShell2.java +++ b/src/test/java/projects/shell/coffee_machine/stage2/test/CoffeeMachineTestShell2.java @@ -3,16 +3,19 @@ import org.hyperskill.hstest.stage.StageTest; import org.hyperskill.hstest.testcase.CheckResult; import org.hyperskill.hstest.testcase.TestCase; +import org.junit.Assume; import org.junit.BeforeClass; import java.util.List; +import static org.hyperskill.hstest.testing.ExecutionOptions.includeProcessTesting; + public class CoffeeMachineTestShell2 extends StageTest { @BeforeClass public static void stopProcessTest() { - //Assume.assumeTrue(includeProcessTesting); + Assume.assumeTrue(includeProcessTesting); } @Override From 31b545b43179330eaba9140a0e699783a6c5ac4f Mon Sep 17 00:00:00 2001 From: Vladimir Turov Date: Fri, 28 Jan 2022 16:37:20 +0300 Subject: [PATCH 26/55] Fix Delombok --- .../execution/runnable/RunnableFile.java | 43 ++----------------- 1 file changed, 3 insertions(+), 40 deletions(-) diff --git a/src/main/java/org/hyperskill/hstest/testing/execution/runnable/RunnableFile.java b/src/main/java/org/hyperskill/hstest/testing/execution/runnable/RunnableFile.java index a10867d4..6c8ee54b 100644 --- a/src/main/java/org/hyperskill/hstest/testing/execution/runnable/RunnableFile.java +++ b/src/main/java/org/hyperskill/hstest/testing/execution/runnable/RunnableFile.java @@ -1,7 +1,10 @@ package org.hyperskill.hstest.testing.execution.runnable; +import lombok.Data; + import java.io.File; +@Data public class RunnableFile { final File folder; final File file; @@ -10,44 +13,4 @@ public RunnableFile(File folder, File file) { this.folder = folder; this.file = file; } - - public File getFolder() { - return this.folder; - } - - public File getFile() { - return this.file; - } - - public boolean equals(final Object o) { - if (o == this) return true; - if (!(o instanceof RunnableFile)) return false; - final RunnableFile other = (RunnableFile) o; - if (!other.canEqual((Object) this)) return false; - final Object this$folder = this.getFolder(); - final Object other$folder = other.getFolder(); - if (this$folder == null ? other$folder != null : !this$folder.equals(other$folder)) return false; - final Object this$file = this.getFile(); - final Object other$file = other.getFile(); - if (this$file == null ? other$file != null : !this$file.equals(other$file)) return false; - return true; - } - - protected boolean canEqual(final Object other) { - return other instanceof RunnableFile; - } - - public int hashCode() { - final int PRIME = 59; - int result = 1; - final Object $folder = this.getFolder(); - result = result * PRIME + ($folder == null ? 43 : $folder.hashCode()); - final Object $file = this.getFile(); - result = result * PRIME + ($file == null ? 43 : $file.hashCode()); - return result; - } - - public String toString() { - return "RunnableFile(folder=" + this.getFolder() + ", file=" + this.getFile() + ")"; - } } From a90516b3693265f0a2aa822a5295c50fd7e4d079 Mon Sep 17 00:00:00 2001 From: Vladimir Turov Date: Fri, 28 Jan 2022 17:42:04 +0300 Subject: [PATCH 27/55] Fix jitpack build --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 4b35ea0e..646178d4 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ plugins { id 'java' id 'java-library' - id 'checkstyle' + // id 'checkstyle' doesn't work with jitpack for some reason // id 'org.jetbrains.kotlin.jvm' version '1.3.60' } From 88026cbaf90add0cbf35e527bd553b4cb55e79aa Mon Sep 17 00:00:00 2001 From: Vladimir Turov Date: Fri, 28 Jan 2022 18:29:40 +0300 Subject: [PATCH 28/55] Update dependencies --- build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 646178d4..f6ee8816 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ plugins { } group 'org.hyperskill' -version '8.2' +version '8.3' sourceCompatibility = 11 @@ -16,13 +16,13 @@ repositories { } dependencies { - api 'junit:junit:4.12' + api 'junit:junit:4.13.2' // implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" api 'org.assertj:assertj-swing-junit:3.17.1' api 'org.apache.httpcomponents:httpclient:4.5.13' - api 'com.google.code.gson:gson:2.8.8' + api 'com.google.code.gson:gson:2.8.9' compileOnly 'org.projectlombok:lombok:1.18.22' annotationProcessor 'org.projectlombok:lombok:1.18.22' From 50b1646f90ee2a4beee4dc0df5087a248e7fd3a9 Mon Sep 17 00:00:00 2001 From: Vladimir Turov Date: Fri, 28 Jan 2022 18:51:47 +0300 Subject: [PATCH 29/55] Bump version --- build.gradle | 2 +- .../java/org/hyperskill/hstest/exception/FailureHandler.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index f6ee8816..d465239d 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ plugins { } group 'org.hyperskill' -version '8.3' +version '9' sourceCompatibility = 11 diff --git a/src/main/java/org/hyperskill/hstest/exception/FailureHandler.java b/src/main/java/org/hyperskill/hstest/exception/FailureHandler.java index 28d3f844..c2f6b068 100644 --- a/src/main/java/org/hyperskill/hstest/exception/FailureHandler.java +++ b/src/main/java/org/hyperskill/hstest/exception/FailureHandler.java @@ -33,7 +33,7 @@ public static String getReport() { + "OS " + os + "\n" + "Java " + java + "\n" + "Vendor " + vendor + "\n" - + "Testing library version 8.2"; + + "Testing library version 9"; } else { return "Submitted via web"; } From 002ed24dc8667cedef5d191bb12423fa232d9a40 Mon Sep 17 00:00:00 2001 From: Vladimir Turov Date: Wed, 2 Feb 2022 13:47:40 +0300 Subject: [PATCH 30/55] Try to use most recent Gradle version in Jitpack --- .gitignore | 6 ------ build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 5 +++++ 3 files changed, 6 insertions(+), 7 deletions(-) create mode 100644 gradle/wrapper/gradle-wrapper.properties diff --git a/.gitignore b/.gitignore index 3ba16c1a..4c08b359 100644 --- a/.gitignore +++ b/.gitignore @@ -36,15 +36,9 @@ classes/ # linux *~ -# Tests and solutions to projects - -src/main/java/solutions/* -src/test/java/solutiontests/* - # gradle build/* -gradle/* .gradle/* gradlew gradlew.bat diff --git a/build.gradle b/build.gradle index d465239d..f34165a9 100644 --- a/build.gradle +++ b/build.gradle @@ -42,7 +42,7 @@ tasks.withType(JavaCompile) { // } wrapper { - gradleVersion = '7.2' + gradleVersion = '7.3' } task resolveDependencies { diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..e750102e --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists From 9908255195d2ed3a9d61cd44bde10ff2a12f0faa Mon Sep 17 00:00:00 2001 From: Vladimir Turov Date: Wed, 2 Feb 2022 14:05:46 +0300 Subject: [PATCH 31/55] Add maven plugin that is required by jitpack --- build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build.gradle b/build.gradle index f34165a9..4f3dbe8c 100644 --- a/build.gradle +++ b/build.gradle @@ -5,6 +5,8 @@ plugins { // id 'org.jetbrains.kotlin.jvm' version '1.3.60' } +apply plugin: 'maven-publish' + group 'org.hyperskill' version '9' From 2f7f3a07fee3ee24eb1e24a7a530513fabaf3dc0 Mon Sep 17 00:00:00 2001 From: Vladimir Turov Date: Wed, 2 Feb 2022 14:08:33 +0300 Subject: [PATCH 32/55] Revert using maven-publish (jitpack fails to build) --- build.gradle | 2 -- 1 file changed, 2 deletions(-) diff --git a/build.gradle b/build.gradle index 4f3dbe8c..f34165a9 100644 --- a/build.gradle +++ b/build.gradle @@ -5,8 +5,6 @@ plugins { // id 'org.jetbrains.kotlin.jvm' version '1.3.60' } -apply plugin: 'maven-publish' - group 'org.hyperskill' version '9' From e4a6d2984e9689993cd62e3f473106c068d88a61 Mon Sep 17 00:00:00 2001 From: Vladimir Turov Date: Thu, 3 Feb 2022 17:56:22 +0300 Subject: [PATCH 33/55] Fix running non-java runner on java solution --- .../hyperskill/hstest/common/FileUtils.java | 19 +++++++++++++++++++ .../hyperskill/hstest/stage/StageTest.java | 8 +++++++- .../runner/AsyncDynamicTestingRunner.java | 5 ----- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/hyperskill/hstest/common/FileUtils.java b/src/main/java/org/hyperskill/hstest/common/FileUtils.java index 3ff1ad32..730ab081 100644 --- a/src/main/java/org/hyperskill/hstest/common/FileUtils.java +++ b/src/main/java/org/hyperskill/hstest/common/FileUtils.java @@ -94,6 +94,25 @@ public static class Folder { final List files; } + public static boolean hasJavaSolution(String folder) { + var currFolder = abspath(folder); + var srcFolder = join(currFolder, "src"); + + if (!isdir(srcFolder)) { + return false; + } + + for (var src : walkUserFiles(srcFolder)) { + for (var file : src.files) { + if (file.getName().endsWith(".java") || file.getName().endsWith(".kt")) { + return true; + } + } + } + + return false; + } + public static Iterable walkUserFiles(String folder) { var currFolder = abspath(folder); var testFolder = join(currFolder, "test"); diff --git a/src/main/java/org/hyperskill/hstest/stage/StageTest.java b/src/main/java/org/hyperskill/hstest/stage/StageTest.java index 1aec623f..a64309dd 100644 --- a/src/main/java/org/hyperskill/hstest/stage/StageTest.java +++ b/src/main/java/org/hyperskill/hstest/stage/StageTest.java @@ -13,6 +13,7 @@ import org.hyperskill.hstest.testcase.CheckResult; import org.hyperskill.hstest.testcase.TestCase; import org.hyperskill.hstest.testing.TestRun; +import org.hyperskill.hstest.testing.execution.MainMethodExecutor; import org.hyperskill.hstest.testing.execution.process.GoExecutor; import org.hyperskill.hstest.testing.execution.process.JavascriptExecutor; import org.hyperskill.hstest.testing.execution.process.PythonExecutor; @@ -28,6 +29,7 @@ import java.util.List; import java.util.stream.Collectors; +import static org.hyperskill.hstest.common.FileUtils.hasJavaSolution; import static org.hyperskill.hstest.common.FileUtils.walkUserFiles; import static org.hyperskill.hstest.dynamic.input.DynamicTesting.searchDynamicTests; import static org.hyperskill.hstest.dynamic.output.ColoredOutput.RED_BOLD; @@ -76,6 +78,10 @@ public StageTest(Class testedClass) { } private TestRunner initRunner() { + if (hasJavaSolution(FileUtils.cwd())) { + return new AsyncDynamicTestingRunner(MainMethodExecutor.class); + } + for (var folder : walkUserFiles(FileUtils.cwd())) { for (var file : folder.getFiles()) { if (file.getName().endsWith(".go")) { @@ -93,7 +99,7 @@ private TestRunner initRunner() { } } - return new AsyncDynamicTestingRunner(); + return new AsyncDynamicTestingRunner(MainMethodExecutor.class); } private List initTests() { diff --git a/src/main/java/org/hyperskill/hstest/testing/runner/AsyncDynamicTestingRunner.java b/src/main/java/org/hyperskill/hstest/testing/runner/AsyncDynamicTestingRunner.java index e02f0f57..57e61f86 100644 --- a/src/main/java/org/hyperskill/hstest/testing/runner/AsyncDynamicTestingRunner.java +++ b/src/main/java/org/hyperskill/hstest/testing/runner/AsyncDynamicTestingRunner.java @@ -11,7 +11,6 @@ import org.hyperskill.hstest.testcase.CheckResult; import org.hyperskill.hstest.testcase.TestCase; import org.hyperskill.hstest.testing.TestRun; -import org.hyperskill.hstest.testing.execution.MainMethodExecutor; import org.hyperskill.hstest.testing.execution.ProgramExecutor; import java.util.concurrent.ExecutionException; @@ -26,10 +25,6 @@ public class AsyncDynamicTestingRunner implements TestRunner { protected Class executor; - public AsyncDynamicTestingRunner() { - this(MainMethodExecutor.class); - } - public AsyncDynamicTestingRunner(Class executor) { this.executor = executor; } From 4250191c71218ea5387f75d81f90781f212b2700 Mon Sep 17 00:00:00 2001 From: Vladimir Turov Date: Thu, 14 Apr 2022 12:59:01 +0300 Subject: [PATCH 34/55] Remove debug code --- src/main/java/org/hyperskill/hstest/common/FileUtils.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/org/hyperskill/hstest/common/FileUtils.java b/src/main/java/org/hyperskill/hstest/common/FileUtils.java index 730ab081..a59ae0e5 100644 --- a/src/main/java/org/hyperskill/hstest/common/FileUtils.java +++ b/src/main/java/org/hyperskill/hstest/common/FileUtils.java @@ -174,9 +174,6 @@ public static String cwd() { } public static void chdir(String folder) { - if (folder.equals("C:\\Users\\Vladimir\\Desktop\\hs-test\\src\\test\\java\\outcomes\\src\\test\\java\\outcomes")) { - System.out.println("!!! " + folder); - } chdir(new File(folder)); } From a75c35f11ddfebbe866f9a16f05fcbe13dbcdb50 Mon Sep 17 00:00:00 2001 From: Vladimir Turov Date: Thu, 21 Apr 2022 15:51:48 +0300 Subject: [PATCH 35/55] Fix initial idle wait --- .../org/hyperskill/hstest/testing/ProcessWrapper.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/java/org/hyperskill/hstest/testing/ProcessWrapper.java b/src/main/java/org/hyperskill/hstest/testing/ProcessWrapper.java index 30d4af78..b009ac6b 100644 --- a/src/main/java/org/hyperskill/hstest/testing/ProcessWrapper.java +++ b/src/main/java/org/hyperskill/hstest/testing/ProcessWrapper.java @@ -43,6 +43,7 @@ public class ProcessWrapper { private final int outputDiffHistoryMax = 2; private boolean initialIdleWait = true; + private int initialIdleWaitTime = 150; @Getter @Setter boolean checkEarlyFinish = false; @Getter @Setter boolean registerOutput = true; @@ -227,6 +228,13 @@ private void checkCpuLoad() { sleep(10); checkAlive(); + + if (initialIdleWait) { + initialIdleWaitTime--; + if (initialIdleWaitTime == 0) { + initialIdleWait = false; + } + } } } From ef5b3ba39d9d8a581738a37f27809fed142319a0 Mon Sep 17 00:00:00 2001 From: Vladimir Turov Date: Wed, 8 Jun 2022 22:50:01 +0300 Subject: [PATCH 36/55] Fix comment --- .../org/hyperskill/hstest/dynamic/input/DynamicTesting.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/hyperskill/hstest/dynamic/input/DynamicTesting.java b/src/main/java/org/hyperskill/hstest/dynamic/input/DynamicTesting.java index 61f0d690..45caad8d 100644 --- a/src/main/java/org/hyperskill/hstest/dynamic/input/DynamicTesting.java +++ b/src/main/java/org/hyperskill/hstest/dynamic/input/DynamicTesting.java @@ -161,7 +161,7 @@ String ejectNextInput(String currOutput) { * * @param obj object that contain methods and variables declared with DynamicTest annotation. * @return list of DynamicMethod objects that represent every method marked - * with DynamicTestingMethod annotation. + * with DynamicTest annotation. */ static > List> searchDynamicTests(T obj) { class DynamicTestElement From acff01375f42c7f04376ff519e1d2ffbce859325 Mon Sep 17 00:00:00 2001 From: TheEmbalmer <53704943+MisterBucketman@users.noreply.github.com> Date: Tue, 21 Jun 2022 11:16:32 +0545 Subject: [PATCH 37/55] Add error handling for UE in Swing tests. --- .../hyperskill/hstest/stage/SwingTest.java | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/hyperskill/hstest/stage/SwingTest.java b/src/main/java/org/hyperskill/hstest/stage/SwingTest.java index 626d9140..59877c9c 100644 --- a/src/main/java/org/hyperskill/hstest/stage/SwingTest.java +++ b/src/main/java/org/hyperskill/hstest/stage/SwingTest.java @@ -2,11 +2,10 @@ import lombok.Getter; import lombok.Setter; -import org.assertj.swing.fixture.AbstractComponentFixture; -import org.assertj.swing.fixture.EditableComponentFixture; -import org.assertj.swing.fixture.FrameFixture; -import org.assertj.swing.fixture.JTextComponentFixture; +import org.assertj.swing.exception.ActionFailedException; +import org.assertj.swing.fixture.*; import org.hyperskill.hstest.dynamic.output.InfiniteLoopDetector; +import org.hyperskill.hstest.exception.outcomes.UnexpectedError; import org.hyperskill.hstest.exception.outcomes.WrongAnswer; import org.hyperskill.hstest.testcase.attach.SwingSettings; import org.hyperskill.hstest.testing.Settings; @@ -79,6 +78,25 @@ public void requireEmpty(JTextComponentFixture... elements) { require(elements, JTextComponentFixture::requireEmpty, "should be empty"); } + public > void click(T button) { + try { + button.click(); + } catch (ActionFailedException ex) { + String name = ((SwingApplicationRunner) runner).fixtureToName(button); + throw new WrongAnswer("The test was unable to click the specific button component. Button text is \"" + name + "\".\n" + + "To mitigate this error, try the following:-\n" + + "1. Do not use the computer while the test is being executed. This maintains focus on the components the test wants to manipulate.\n" + + "2. Make sure the component is present and is within the boundaries of the program screen and can be clicked.\n" + + "3. There is something that is blocking the test from manipulating components in the screen. It should be removed."); + } catch (NullPointerException ex) { + String name = ((SwingApplicationRunner) runner).fixtureToName(button); + throw new WrongAnswer("Null pointer exception occurred due to component \"" + name + "\"."); + } catch (IllegalStateException ex) { + String name = ((SwingApplicationRunner) runner).fixtureToName(button); + throw new WrongAnswer("The component \"" + name + "\" should be enabled and showing on the screen."); + } + } + public static List getAllComponents(final Container c) { Component[] comps = c.getComponents(); List compList = new ArrayList<>(); From 561984b137da6686ac5640363285330ba34c66fb Mon Sep 17 00:00:00 2001 From: tanya Date: Sat, 25 Jun 2022 16:56:09 +0300 Subject: [PATCH 38/55] Get rid of passing user class in SpringTest class --- .../hstest/common/ReflectionUtils.java | 15 ++++++ .../hyperskill/hstest/stage/SpringTest.java | 49 ++++++++++++++++--- 2 files changed, 57 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/hyperskill/hstest/common/ReflectionUtils.java b/src/main/java/org/hyperskill/hstest/common/ReflectionUtils.java index 1397d052..be9541ea 100644 --- a/src/main/java/org/hyperskill/hstest/common/ReflectionUtils.java +++ b/src/main/java/org/hyperskill/hstest/common/ReflectionUtils.java @@ -252,4 +252,19 @@ public static > void setupCwd(T stage) { FileUtils.chdir(testDir); } + + public static List> getTypesAnnotatedWith(String annotationPath) { + return ReflectionUtils + .getAllClassesFromPackage("") + .stream() + .filter(clazz -> { + for (var annotation : clazz.getDeclaredAnnotations()) { + if (annotation.getClass().getCanonicalName().equals(annotationPath)) { + return true; + } + } + return false; + }) + .collect(Collectors.toList()); + } } diff --git a/src/main/java/org/hyperskill/hstest/stage/SpringTest.java b/src/main/java/org/hyperskill/hstest/stage/SpringTest.java index 8d60ada7..31e94c68 100644 --- a/src/main/java/org/hyperskill/hstest/stage/SpringTest.java +++ b/src/main/java/org/hyperskill/hstest/stage/SpringTest.java @@ -25,6 +25,7 @@ import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import static org.hyperskill.hstest.common.Utils.tryManyTimes; import static org.hyperskill.hstest.mocks.web.constants.Methods.DELETE; @@ -39,7 +40,6 @@ public abstract class SpringTest extends StageTest { private static boolean isTearDown = false; private static boolean springRunning = false; - private static Class springClass; private static String[] args; protected final int port; @@ -50,28 +50,47 @@ public static void launchSpring(String[] args) throws Exception { startSpring(); } + public SpringTest() { + this(detectPort()); + } + + @Deprecated public SpringTest(Class clazz) { - this(clazz, detectPort()); + this(); } - public SpringTest(Class clazz, int port) { + public SpringTest(int port) { InfiniteLoopDetector.setWorking(false); Settings.doResetOutput = false; runner = new SpringApplicationRunner(); - springClass = clazz; this.port = port; } + @Deprecated + public SpringTest(Class clazz, int port) { + this(port); + } + + public SpringTest(String database) { + this(detectPort(), database); + } + + @Deprecated public SpringTest(Class clazz, String database) { - this(clazz, detectPort(), database); + this(database); } - public SpringTest(Class clazz, int port, String database) { - this(clazz, port); + public SpringTest(int port, String database) { + this(port); this.databasePath = database; replaceDatabase(); } + @Deprecated + public SpringTest(Class clazz, int port, String database) { + this(port, database); + } + private static int detectPort() { String[] resourcesDirs = new String[] { "resources", "src" + File.separator + "resources" @@ -135,6 +154,22 @@ public void tearDown() { public static void startSpring() throws Exception { if (!springRunning) { + String annotationPath = "org.springframework.boot.autoconfigure.SpringBootApplication"; + List> suitableClasses = ReflectionUtils.getTypesAnnotatedWith(annotationPath); + + int length = suitableClasses.size(); + if (length == 0) { + throw new UnexpectedError("No class found with annotation " + annotationPath); + } else if (length > 1) { + throw new UnexpectedError( + "More than one class found with annotation " + annotationPath + "\n" + + "Found classes: " + suitableClasses.stream() + .map(Class::getCanonicalName) + .collect(Collectors.joining(", ")) + ); + } + + Class springClass = suitableClasses.get(0); Method mainMethod = ReflectionUtils.getMainMethod(springClass); mainMethod.invoke(null, new Object[] {args}); springRunning = true; From 91f5d6b07e50af07d084a026f68c35179f752527 Mon Sep 17 00:00:00 2001 From: tanya Date: Sun, 26 Jun 2022 09:01:36 +0300 Subject: [PATCH 39/55] Add filter by has main method --- src/main/java/org/hyperskill/hstest/common/ReflectionUtils.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/hyperskill/hstest/common/ReflectionUtils.java b/src/main/java/org/hyperskill/hstest/common/ReflectionUtils.java index be9541ea..612f9d24 100644 --- a/src/main/java/org/hyperskill/hstest/common/ReflectionUtils.java +++ b/src/main/java/org/hyperskill/hstest/common/ReflectionUtils.java @@ -265,6 +265,7 @@ public static List> getTypesAnnotatedWith(String annotationPath) { } return false; }) + .filter(ReflectionUtils::hasMainMethod) .collect(Collectors.toList()); } } From 9e31b098b67ddb9dde4570696c33a87b11ac2fb0 Mon Sep 17 00:00:00 2001 From: tanya Date: Sun, 26 Jun 2022 10:03:41 +0300 Subject: [PATCH 40/55] Add vars for debug --- .../java/org/hyperskill/hstest/common/ReflectionUtils.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/hyperskill/hstest/common/ReflectionUtils.java b/src/main/java/org/hyperskill/hstest/common/ReflectionUtils.java index 612f9d24..2cfdd1c8 100644 --- a/src/main/java/org/hyperskill/hstest/common/ReflectionUtils.java +++ b/src/main/java/org/hyperskill/hstest/common/ReflectionUtils.java @@ -259,7 +259,9 @@ public static List> getTypesAnnotatedWith(String annotationPath) { .stream() .filter(clazz -> { for (var annotation : clazz.getDeclaredAnnotations()) { - if (annotation.getClass().getCanonicalName().equals(annotationPath)) { + Class annotationClass = annotation.getClass(); + String annotationName = annotationClass.getCanonicalName(); + if (annotationName.equals(annotationPath)) { return true; } } From 6bf46925457f1cad76f66c3a79c42c90123cf748 Mon Sep 17 00:00:00 2001 From: tanya Date: Sun, 26 Jun 2022 10:13:21 +0300 Subject: [PATCH 41/55] Get real annotation class --- src/main/java/org/hyperskill/hstest/common/ReflectionUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/hyperskill/hstest/common/ReflectionUtils.java b/src/main/java/org/hyperskill/hstest/common/ReflectionUtils.java index 2cfdd1c8..14b8a55f 100644 --- a/src/main/java/org/hyperskill/hstest/common/ReflectionUtils.java +++ b/src/main/java/org/hyperskill/hstest/common/ReflectionUtils.java @@ -259,7 +259,7 @@ public static List> getTypesAnnotatedWith(String annotationPath) { .stream() .filter(clazz -> { for (var annotation : clazz.getDeclaredAnnotations()) { - Class annotationClass = annotation.getClass(); + Class annotationClass = annotation.annotationType(); String annotationName = annotationClass.getCanonicalName(); if (annotationName.equals(annotationPath)) { return true; From b0dd86a5d45c0918d012ee9743fd5c50fc8f5b4d Mon Sep 17 00:00:00 2001 From: tanya Date: Sun, 26 Jun 2022 10:16:25 +0300 Subject: [PATCH 42/55] Refactor --- .../java/org/hyperskill/hstest/common/ReflectionUtils.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/org/hyperskill/hstest/common/ReflectionUtils.java b/src/main/java/org/hyperskill/hstest/common/ReflectionUtils.java index 14b8a55f..355de10f 100644 --- a/src/main/java/org/hyperskill/hstest/common/ReflectionUtils.java +++ b/src/main/java/org/hyperskill/hstest/common/ReflectionUtils.java @@ -259,9 +259,7 @@ public static List> getTypesAnnotatedWith(String annotationPath) { .stream() .filter(clazz -> { for (var annotation : clazz.getDeclaredAnnotations()) { - Class annotationClass = annotation.annotationType(); - String annotationName = annotationClass.getCanonicalName(); - if (annotationName.equals(annotationPath)) { + if (annotation.annotationType().getCanonicalName().equals(annotationPath)) { return true; } } From 4f7f2b8f5dbaad07174fe3604c3a334b3c22eacb Mon Sep 17 00:00:00 2001 From: tanya Date: Mon, 27 Jun 2022 15:39:04 +0300 Subject: [PATCH 43/55] Code review fixes --- .../hyperskill/hstest/common/ReflectionUtils.java | 3 +-- .../org/hyperskill/hstest/stage/SpringTest.java | 13 +++++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/hyperskill/hstest/common/ReflectionUtils.java b/src/main/java/org/hyperskill/hstest/common/ReflectionUtils.java index 355de10f..fd7b63f0 100644 --- a/src/main/java/org/hyperskill/hstest/common/ReflectionUtils.java +++ b/src/main/java/org/hyperskill/hstest/common/ReflectionUtils.java @@ -253,7 +253,7 @@ public static > void setupCwd(T stage) { FileUtils.chdir(testDir); } - public static List> getTypesAnnotatedWith(String annotationPath) { + public static List> getClassesAnnotatedWith(String annotationPath) { return ReflectionUtils .getAllClassesFromPackage("") .stream() @@ -265,7 +265,6 @@ public static List> getTypesAnnotatedWith(String annotationPath) { } return false; }) - .filter(ReflectionUtils::hasMainMethod) .collect(Collectors.toList()); } } diff --git a/src/main/java/org/hyperskill/hstest/stage/SpringTest.java b/src/main/java/org/hyperskill/hstest/stage/SpringTest.java index 31e94c68..154a0205 100644 --- a/src/main/java/org/hyperskill/hstest/stage/SpringTest.java +++ b/src/main/java/org/hyperskill/hstest/stage/SpringTest.java @@ -6,6 +6,7 @@ import org.hyperskill.hstest.common.ReflectionUtils; import org.hyperskill.hstest.dynamic.output.InfiniteLoopDetector; import org.hyperskill.hstest.dynamic.output.OutputHandler; +import org.hyperskill.hstest.exception.outcomes.ErrorWithFeedback; import org.hyperskill.hstest.exception.outcomes.UnexpectedError; import org.hyperskill.hstest.exception.outcomes.WrongAnswer; import org.hyperskill.hstest.mocks.web.request.HttpRequest; @@ -155,14 +156,18 @@ public void tearDown() { public static void startSpring() throws Exception { if (!springRunning) { String annotationPath = "org.springframework.boot.autoconfigure.SpringBootApplication"; - List> suitableClasses = ReflectionUtils.getTypesAnnotatedWith(annotationPath); + List> suitableClasses = ReflectionUtils.getClassesAnnotatedWith(annotationPath) + .stream() + .filter(ReflectionUtils::hasMainMethod) + .collect(Collectors.toList());; int length = suitableClasses.size(); if (length == 0) { - throw new UnexpectedError("No class found with annotation " + annotationPath); + throw new ErrorWithFeedback("No class found with annotation " + annotationPath); } else if (length > 1) { - throw new UnexpectedError( - "More than one class found with annotation " + annotationPath + "\n" + + throw new ErrorWithFeedback( + "More than one class found with annotation " + annotationPath + + " , please leave only 1 class with this annotation." + "\n" + "Found classes: " + suitableClasses.stream() .map(Class::getCanonicalName) .collect(Collectors.joining(", ")) From 49fb9f6a462da6da71cda3199df109efd634dce4 Mon Sep 17 00:00:00 2001 From: tanya Date: Fri, 8 Jul 2022 16:57:10 +0300 Subject: [PATCH 44/55] Don't fallback to an empty package when looking for a class/executable file --- .../org/hyperskill/hstest/common/Utils.java | 4 +++ .../testing/execution/MainMethodExecutor.java | 8 ++++- .../execution/searcher/BaseSearcher.java | 6 ++-- .../NoFindExecutableFileByDirectory.java | 27 ++++++++++++++++ .../main.go | 9 ++++++ .../NoFindExecutableFileByFilePath.java | 27 ++++++++++++++++ .../main.go | 9 ++++++ .../FallbackToClassPackage.java | 32 +++++++++++++++++++ .../fallback_to_class_package/Main.java | 7 ++++ .../NoClassByClassName.java | 26 +++++++++++++++ .../NoPackageByPackageName.java | 26 +++++++++++++++ 11 files changed, 178 insertions(+), 3 deletions(-) create mode 100644 src/test/java/outcomes/process_executor/go/no_find_executable_file_by_directory/NoFindExecutableFileByDirectory.java create mode 100644 src/test/java/outcomes/process_executor/go/no_find_executable_file_by_directory/main.go create mode 100644 src/test/java/outcomes/process_executor/go/no_find_executable_file_by_file_path/NoFindExecutableFileByFilePath.java create mode 100644 src/test/java/outcomes/process_executor/go/no_find_executable_file_by_file_path/main.go create mode 100644 src/test/java/outcomes/separate_package/fallback_to_class_package/FallbackToClassPackage.java create mode 100644 src/test/java/outcomes/separate_package/fallback_to_class_package/Main.java create mode 100644 src/test/java/outcomes/separate_package/no_class_by_class_name/NoClassByClassName.java create mode 100644 src/test/java/outcomes/separate_package/no_package_by_package_name/NoPackageByPackageName.java diff --git a/src/main/java/org/hyperskill/hstest/common/Utils.java b/src/main/java/org/hyperskill/hstest/common/Utils.java index b7284584..7d7e22a5 100644 --- a/src/main/java/org/hyperskill/hstest/common/Utils.java +++ b/src/main/java/org/hyperskill/hstest/common/Utils.java @@ -193,4 +193,8 @@ public int compareTo(NameTokenizer o) { return method1.compareTo(method2); } + + public static boolean isPackageName(String name) { + return name.matches("^[a-z_]+(\\.[a-z_]+)*"); + } } diff --git a/src/main/java/org/hyperskill/hstest/testing/execution/MainMethodExecutor.java b/src/main/java/org/hyperskill/hstest/testing/execution/MainMethodExecutor.java index ef32d2ce..89dd2799 100644 --- a/src/main/java/org/hyperskill/hstest/testing/execution/MainMethodExecutor.java +++ b/src/main/java/org/hyperskill/hstest/testing/execution/MainMethodExecutor.java @@ -20,6 +20,7 @@ import static java.util.stream.Collectors.toList; import static org.hyperskill.hstest.common.ProcessUtils.newDaemonThreadPool; import static org.hyperskill.hstest.common.ReflectionUtils.getMainMethod; +import static org.hyperskill.hstest.common.Utils.isPackageName; import static org.hyperskill.hstest.exception.FailureHandler.getUserException; import static org.hyperskill.hstest.stage.StageTest.LIB_TEST_PACKAGE; import static org.hyperskill.hstest.testing.execution.ProgramExecutor.ProgramState.EXCEPTION_THROWN; @@ -72,6 +73,10 @@ private void initByClassInstance(Class clazz) { private void initByName(String sourceName) { if (Package.getPackage(sourceName) != null) { initByPackageName(sourceName); + } else if (isPackageName(sourceName)){ + throw new ErrorWithFeedback("Cannot find a class with a main method in package " + + "\"" + sourceName + "\".\n" + + "Check if you declared it as \"public static void main(String[] args)\"."); } else { initByClassName(sourceName); } @@ -86,7 +91,8 @@ private void initByClassName(String className) { Class clazz = Thread.currentThread().getContextClassLoader().loadClass(className); initByClassInstance(clazz); } catch (ClassNotFoundException | NoClassDefFoundError ex) { - initByNothing(className); + throw new ErrorWithFeedback("Cannot find a class with a main method.\n" + + "Check if you declared it as \"public static void main(String[] args)\"."); } } diff --git a/src/main/java/org/hyperskill/hstest/testing/execution/searcher/BaseSearcher.java b/src/main/java/org/hyperskill/hstest/testing/execution/searcher/BaseSearcher.java index ed077582..6e6cc1af 100644 --- a/src/main/java/org/hyperskill/hstest/testing/execution/searcher/BaseSearcher.java +++ b/src/main/java/org/hyperskill/hstest/testing/execution/searcher/BaseSearcher.java @@ -247,8 +247,10 @@ public RunnableFile find(String source) { return new RunnableFile(new File(folder), new File(file + ext)); } else { - return searchCached(null, - null, null, null, null); + var index = sourceModule.lastIndexOf(moduleSeparator); + var path = sourceModule.substring(0, index); + var folder = FileUtils.abspath(path.replace(moduleSeparator, File.separator)); + throw new ErrorWithFeedback("Cannot find a file to execute your code in directory \"" + folder + "\"."); } } diff --git a/src/test/java/outcomes/process_executor/go/no_find_executable_file_by_directory/NoFindExecutableFileByDirectory.java b/src/test/java/outcomes/process_executor/go/no_find_executable_file_by_directory/NoFindExecutableFileByDirectory.java new file mode 100644 index 00000000..1c968016 --- /dev/null +++ b/src/test/java/outcomes/process_executor/go/no_find_executable_file_by_directory/NoFindExecutableFileByDirectory.java @@ -0,0 +1,27 @@ +package outcomes.process_executor.go.no_find_executable_file_by_directory; + +import org.hyperskill.hstest.common.FileUtils; +import org.hyperskill.hstest.dynamic.input.DynamicTestingMethod; +import org.hyperskill.hstest.testcase.CheckResult; +import org.hyperskill.hstest.testing.TestedProgram; +import outcomes.base.ContainsMessage; +import outcomes.base.UserErrorTest; + + +public class NoFindExecutableFileByDirectory extends UserErrorTest { + + @ContainsMessage + String m = + "Error in test #1\n" + + "\n" + + "Cannot find a file to execute your code in directory \"" + FileUtils.cwd() + + "/src/test/java/outcomes/process_executor/go/no_find_executable_file_by_directory\"."; + + @DynamicTestingMethod + CheckResult test() { + TestedProgram main = new TestedProgram( + FileUtils.cwd() + "/non_existent_directory"); + return CheckResult.correct(); + } + +} diff --git a/src/test/java/outcomes/process_executor/go/no_find_executable_file_by_directory/main.go b/src/test/java/outcomes/process_executor/go/no_find_executable_file_by_directory/main.go new file mode 100644 index 00000000..1e9375ad --- /dev/null +++ b/src/test/java/outcomes/process_executor/go/no_find_executable_file_by_directory/main.go @@ -0,0 +1,9 @@ +package main + +import ( + "fmt" +) + +func main() { + Println(`12123123`) +} \ No newline at end of file diff --git a/src/test/java/outcomes/process_executor/go/no_find_executable_file_by_file_path/NoFindExecutableFileByFilePath.java b/src/test/java/outcomes/process_executor/go/no_find_executable_file_by_file_path/NoFindExecutableFileByFilePath.java new file mode 100644 index 00000000..5039d3ef --- /dev/null +++ b/src/test/java/outcomes/process_executor/go/no_find_executable_file_by_file_path/NoFindExecutableFileByFilePath.java @@ -0,0 +1,27 @@ +package outcomes.process_executor.go.no_find_executable_file_by_file_path; + +import org.hyperskill.hstest.common.FileUtils; +import org.hyperskill.hstest.dynamic.input.DynamicTestingMethod; +import org.hyperskill.hstest.testcase.CheckResult; +import org.hyperskill.hstest.testing.TestedProgram; +import outcomes.base.ContainsMessage; +import outcomes.base.UserErrorTest; + + +public class NoFindExecutableFileByFilePath extends UserErrorTest { + + @ContainsMessage + String m = + "Error in test #1\n" + + "\n" + + "Cannot find a file to execute your code in directory \"" + FileUtils.cwd() + + "/src/test/java/outcomes/process_executor/go/no_find_executable_file_by_file_path\"."; + + @DynamicTestingMethod + CheckResult test() { + TestedProgram main = new TestedProgram( + FileUtils.cwd() + "/non_existent_file.go"); + return CheckResult.correct(); + } + +} \ No newline at end of file diff --git a/src/test/java/outcomes/process_executor/go/no_find_executable_file_by_file_path/main.go b/src/test/java/outcomes/process_executor/go/no_find_executable_file_by_file_path/main.go new file mode 100644 index 00000000..1e9375ad --- /dev/null +++ b/src/test/java/outcomes/process_executor/go/no_find_executable_file_by_file_path/main.go @@ -0,0 +1,9 @@ +package main + +import ( + "fmt" +) + +func main() { + Println(`12123123`) +} \ No newline at end of file diff --git a/src/test/java/outcomes/separate_package/fallback_to_class_package/FallbackToClassPackage.java b/src/test/java/outcomes/separate_package/fallback_to_class_package/FallbackToClassPackage.java new file mode 100644 index 00000000..091e5d32 --- /dev/null +++ b/src/test/java/outcomes/separate_package/fallback_to_class_package/FallbackToClassPackage.java @@ -0,0 +1,32 @@ +package outcomes.separate_package.fallback_to_class_package; + +import org.hyperskill.hstest.stage.StageTest; +import org.hyperskill.hstest.testcase.CheckResult; +import org.hyperskill.hstest.testcase.TestCase; + +import java.util.Arrays; +import java.util.List; + +class ClassWithoutMainMethod { + +} + +public class FallbackToClassPackage extends StageTest { + + public FallbackToClassPackage() { + super(FallbackToClassPackage.class); + } + + @Override + public List generate() { + return Arrays.asList( + new TestCase() + ); + } + + @Override + public CheckResult check(String reply, Object attach) { + return CheckResult.correct(); + } + +} diff --git a/src/test/java/outcomes/separate_package/fallback_to_class_package/Main.java b/src/test/java/outcomes/separate_package/fallback_to_class_package/Main.java new file mode 100644 index 00000000..e29d593c --- /dev/null +++ b/src/test/java/outcomes/separate_package/fallback_to_class_package/Main.java @@ -0,0 +1,7 @@ +package outcomes.separate_package.fallback_to_class_package; + +class Main { + public static void main(String[] args) { + System.out.print("Class with main method"); + } +} diff --git a/src/test/java/outcomes/separate_package/no_class_by_class_name/NoClassByClassName.java b/src/test/java/outcomes/separate_package/no_class_by_class_name/NoClassByClassName.java new file mode 100644 index 00000000..9ca69f66 --- /dev/null +++ b/src/test/java/outcomes/separate_package/no_class_by_class_name/NoClassByClassName.java @@ -0,0 +1,26 @@ +package outcomes.separate_package.no_class_by_class_name; + +import org.hyperskill.hstest.dynamic.input.DynamicTestingMethod; +import org.hyperskill.hstest.testcase.CheckResult; +import org.hyperskill.hstest.testing.TestedProgram; +import outcomes.base.ContainsMessage; +import outcomes.base.UserErrorTest; + + +public class NoClassByClassName extends UserErrorTest { + + @ContainsMessage + String m = + "Error in test #1\n" + + "\n" + + "Cannot find a class with a main method.\n" + + "Check if you declared it as \"public static void main(String[] args)\"."; + + @DynamicTestingMethod + CheckResult test() { + TestedProgram main = new TestedProgram( + "outcomes.separate_package.no_class_by_class_name.NonExistentClass"); + return CheckResult.correct(); + } + +} diff --git a/src/test/java/outcomes/separate_package/no_package_by_package_name/NoPackageByPackageName.java b/src/test/java/outcomes/separate_package/no_package_by_package_name/NoPackageByPackageName.java new file mode 100644 index 00000000..6a7ae08f --- /dev/null +++ b/src/test/java/outcomes/separate_package/no_package_by_package_name/NoPackageByPackageName.java @@ -0,0 +1,26 @@ +package outcomes.separate_package.no_package_by_package_name; + +import org.hyperskill.hstest.dynamic.input.DynamicTestingMethod; +import org.hyperskill.hstest.testcase.CheckResult; +import org.hyperskill.hstest.testing.TestedProgram; +import outcomes.base.ContainsMessage; +import outcomes.base.UserErrorTest; + + +public class NoPackageByPackageName extends UserErrorTest { + + @ContainsMessage + String m = + "Error in test #1\n" + + "\n" + + "Cannot find a class with a main method in package \"outcomes.separate_package.non_existent_package\".\n" + + "Check if you declared it as \"public static void main(String[] args)\"."; + + @DynamicTestingMethod + CheckResult test() { + TestedProgram main = new TestedProgram( + "outcomes.separate_package.non_existent_package"); + return CheckResult.correct(); + } + +} From be5cf992a5ce1fdc303341ffd23a846a04027213 Mon Sep 17 00:00:00 2001 From: tanya Date: Mon, 11 Jul 2022 19:23:33 +0300 Subject: [PATCH 45/55] Add tests for ProcessExecutor & refactor code --- .../org/hyperskill/hstest/common/Utils.java | 4 -- .../testing/execution/MainMethodExecutor.java | 41 +++++++++---------- .../execution/searcher/BaseSearcher.java | 2 +- .../FindExecutableFileByDirectory.java | 17 ++++++++ .../directory_to_find/main.go | 7 ++++ .../find_executable_file_by_directory/main.go | 1 + .../FindExecutableFileByFileName.java | 17 ++++++++ .../find_executable_file_by_filename/main.go | 7 ++++ .../FindMoreThenOneExecutableFile.java | 30 ++++++++++++++ .../directory/main1.go | 7 ++++ .../directory/main2.go | 7 ++++ .../main.go | 1 + .../NoExecutableFilesAtDirectory.java | 20 +++++++++ .../no_executable_files_at_directory/main.go | 1 + .../NoFindExecutableFileByDirectory.java | 17 ++++---- .../main.go | 10 +---- .../NoFindExecutableFileByFilePath.java | 16 ++++---- .../main.go | 10 +---- .../FallbackToClassPackage.java | 32 --------------- .../NoClassByClassName.java | 4 +- .../Main.java | 2 +- .../NoFallbackToClassPackage.java | 34 +++++++++++++++ .../NoPackageByPackageName.java | 8 ++-- 23 files changed, 196 insertions(+), 99 deletions(-) create mode 100644 src/test/java/outcomes/process_executor/go/find_executable_file_by_directory/FindExecutableFileByDirectory.java create mode 100644 src/test/java/outcomes/process_executor/go/find_executable_file_by_directory/directory_to_find/main.go create mode 100644 src/test/java/outcomes/process_executor/go/find_executable_file_by_directory/main.go create mode 100644 src/test/java/outcomes/process_executor/go/find_executable_file_by_filename/FindExecutableFileByFileName.java create mode 100644 src/test/java/outcomes/process_executor/go/find_executable_file_by_filename/main.go create mode 100644 src/test/java/outcomes/process_executor/go/find_more_then_one_executable_file/FindMoreThenOneExecutableFile.java create mode 100644 src/test/java/outcomes/process_executor/go/find_more_then_one_executable_file/directory/main1.go create mode 100644 src/test/java/outcomes/process_executor/go/find_more_then_one_executable_file/directory/main2.go create mode 100644 src/test/java/outcomes/process_executor/go/find_more_then_one_executable_file/main.go create mode 100644 src/test/java/outcomes/process_executor/go/no_executable_files_at_directory/NoExecutableFilesAtDirectory.java create mode 100644 src/test/java/outcomes/process_executor/go/no_executable_files_at_directory/main.go delete mode 100644 src/test/java/outcomes/separate_package/fallback_to_class_package/FallbackToClassPackage.java rename src/test/java/outcomes/separate_package/{fallback_to_class_package => no_fallback_to_class_package}/Main.java (65%) create mode 100644 src/test/java/outcomes/separate_package/no_fallback_to_class_package/NoFallbackToClassPackage.java diff --git a/src/main/java/org/hyperskill/hstest/common/Utils.java b/src/main/java/org/hyperskill/hstest/common/Utils.java index 7d7e22a5..b7284584 100644 --- a/src/main/java/org/hyperskill/hstest/common/Utils.java +++ b/src/main/java/org/hyperskill/hstest/common/Utils.java @@ -193,8 +193,4 @@ public int compareTo(NameTokenizer o) { return method1.compareTo(method2); } - - public static boolean isPackageName(String name) { - return name.matches("^[a-z_]+(\\.[a-z_]+)*"); - } } diff --git a/src/main/java/org/hyperskill/hstest/testing/execution/MainMethodExecutor.java b/src/main/java/org/hyperskill/hstest/testing/execution/MainMethodExecutor.java index 89dd2799..1ace6858 100644 --- a/src/main/java/org/hyperskill/hstest/testing/execution/MainMethodExecutor.java +++ b/src/main/java/org/hyperskill/hstest/testing/execution/MainMethodExecutor.java @@ -20,7 +20,6 @@ import static java.util.stream.Collectors.toList; import static org.hyperskill.hstest.common.ProcessUtils.newDaemonThreadPool; import static org.hyperskill.hstest.common.ReflectionUtils.getMainMethod; -import static org.hyperskill.hstest.common.Utils.isPackageName; import static org.hyperskill.hstest.exception.FailureHandler.getUserException; import static org.hyperskill.hstest.stage.StageTest.LIB_TEST_PACKAGE; import static org.hyperskill.hstest.testing.execution.ProgramExecutor.ProgramState.EXCEPTION_THROWN; @@ -59,12 +58,8 @@ public MainMethodExecutor(String sourceName) { private void initByClassInstance(Class clazz) { if (!ReflectionUtils.hasMainMethod(clazz)) { - if (clazz.getName().startsWith(LIB_TEST_PACKAGE)) { - initByNothing(clazz.getPackage().getName(), false); - } else { - initByNothing(); - } - return; + String errorMessage = getNotFoundClassWithMainMethodMessage(clazz.getPackage().getName()); + throw new ErrorWithFeedback(errorMessage); } runClass = clazz; @@ -73,10 +68,6 @@ private void initByClassInstance(Class clazz) { private void initByName(String sourceName) { if (Package.getPackage(sourceName) != null) { initByPackageName(sourceName); - } else if (isPackageName(sourceName)){ - throw new ErrorWithFeedback("Cannot find a class with a main method in package " + - "\"" + sourceName + "\".\n" + - "Check if you declared it as \"public static void main(String[] args)\"."); } else { initByClassName(sourceName); } @@ -91,8 +82,8 @@ private void initByClassName(String className) { Class clazz = Thread.currentThread().getContextClassLoader().loadClass(className); initByClassInstance(clazz); } catch (ClassNotFoundException | NoClassDefFoundError ex) { - throw new ErrorWithFeedback("Cannot find a class with a main method.\n" + - "Check if you declared it as \"public static void main(String[] args)\"."); + String errorMessage = getNotFoundClassWithMainMethodMessage(""); + throw new ErrorWithFeedback(errorMessage); } } @@ -113,19 +104,13 @@ private void initByNothing(String userPackage, boolean tryEmptyPackage) { int count = classesWithMainMethod.size(); - String inPackage = ""; - if (!userPackage.isEmpty()) { - inPackage = " in package \"" + userPackage + "\""; - } - if (count == 0) { if (tryEmptyPackage) { initByNothing("", false); return; } - throw new ErrorWithFeedback( - "Cannot find a class with a main method" + inPackage + ".\n" + - "Check if you declared it as \"public static void main(String[] args)\"."); + String errorMessage = getNotFoundClassWithMainMethodMessage(userPackage); + throw new ErrorWithFeedback(errorMessage); } if (count > 1) { @@ -140,6 +125,11 @@ private void initByNothing(String userPackage, boolean tryEmptyPackage) { .sorted() .collect(joining(", ")); + String inPackage = ""; + if (!userPackage.isEmpty()) { + inPackage = " in package \"" + userPackage + "\""; + } + throw new ErrorWithFeedback( "There are " + count + " classes with main method" + inPackage + ": " + allClassesNames + ".\n" @@ -245,4 +235,13 @@ public void setUseSeparateClassLoader(boolean value) { this.useSeparateClassLoader = value; } + private String getNotFoundClassWithMainMethodMessage(String userPackage) { + String inPackage = ""; + if (!userPackage.isEmpty()) { + inPackage = " in package \"" + userPackage + "\""; + } + return "Cannot find a class with a main method" + inPackage + ".\n" + + "Check if you declared it as \"public static void main(String[] args)\"."; + } + } diff --git a/src/main/java/org/hyperskill/hstest/testing/execution/searcher/BaseSearcher.java b/src/main/java/org/hyperskill/hstest/testing/execution/searcher/BaseSearcher.java index 6e6cc1af..f2d4e2af 100644 --- a/src/main/java/org/hyperskill/hstest/testing/execution/searcher/BaseSearcher.java +++ b/src/main/java/org/hyperskill/hstest/testing/execution/searcher/BaseSearcher.java @@ -241,7 +241,7 @@ public RunnableFile find(String source) { } else if (sourceFile != null && FileUtils.isfile(sourceFile)) { var index = sourceModule.lastIndexOf(moduleSeparator); - var path = sourceModule.substring(0, index); + var path = sourceModule.substring(0, Math.max(index, 0)); var file = sourceModule.substring(index + 1); var folder = FileUtils.abspath(path.replace(moduleSeparator, File.separator)); return new RunnableFile(new File(folder), new File(file + ext)); diff --git a/src/test/java/outcomes/process_executor/go/find_executable_file_by_directory/FindExecutableFileByDirectory.java b/src/test/java/outcomes/process_executor/go/find_executable_file_by_directory/FindExecutableFileByDirectory.java new file mode 100644 index 00000000..5e3f4b58 --- /dev/null +++ b/src/test/java/outcomes/process_executor/go/find_executable_file_by_directory/FindExecutableFileByDirectory.java @@ -0,0 +1,17 @@ +package outcomes.process_executor.go.find_executable_file_by_directory; + +import org.hyperskill.hstest.dynamic.DynamicTest; +import org.hyperskill.hstest.stage.StageTest; +import org.hyperskill.hstest.testcase.CheckResult; +import org.hyperskill.hstest.testing.TestedProgram; + + +public class FindExecutableFileByDirectory extends StageTest { + + @DynamicTest + CheckResult test() { + TestedProgram main = new TestedProgram("directory_to_find"); + String out = main.start(); + return new CheckResult("321\n".equals(out), ""); + } +} diff --git a/src/test/java/outcomes/process_executor/go/find_executable_file_by_directory/directory_to_find/main.go b/src/test/java/outcomes/process_executor/go/find_executable_file_by_directory/directory_to_find/main.go new file mode 100644 index 00000000..097cc34b --- /dev/null +++ b/src/test/java/outcomes/process_executor/go/find_executable_file_by_directory/directory_to_find/main.go @@ -0,0 +1,7 @@ +package main + +import "fmt" + +func main() { + fmt.Println(`321`) +} \ No newline at end of file diff --git a/src/test/java/outcomes/process_executor/go/find_executable_file_by_directory/main.go b/src/test/java/outcomes/process_executor/go/find_executable_file_by_directory/main.go new file mode 100644 index 00000000..85f0393b --- /dev/null +++ b/src/test/java/outcomes/process_executor/go/find_executable_file_by_directory/main.go @@ -0,0 +1 @@ +package main \ No newline at end of file diff --git a/src/test/java/outcomes/process_executor/go/find_executable_file_by_filename/FindExecutableFileByFileName.java b/src/test/java/outcomes/process_executor/go/find_executable_file_by_filename/FindExecutableFileByFileName.java new file mode 100644 index 00000000..a66e66d0 --- /dev/null +++ b/src/test/java/outcomes/process_executor/go/find_executable_file_by_filename/FindExecutableFileByFileName.java @@ -0,0 +1,17 @@ +package outcomes.process_executor.go.find_executable_file_by_filename; + +import org.hyperskill.hstest.dynamic.DynamicTest; +import org.hyperskill.hstest.stage.StageTest; +import org.hyperskill.hstest.testcase.CheckResult; +import org.hyperskill.hstest.testing.TestedProgram; + + +public class FindExecutableFileByFileName extends StageTest { + + @DynamicTest + CheckResult test() { + TestedProgram main = new TestedProgram("main.go"); + String out = main.start(); + return new CheckResult("123\n".equals(out), ""); + } +} diff --git a/src/test/java/outcomes/process_executor/go/find_executable_file_by_filename/main.go b/src/test/java/outcomes/process_executor/go/find_executable_file_by_filename/main.go new file mode 100644 index 00000000..c9d41df3 --- /dev/null +++ b/src/test/java/outcomes/process_executor/go/find_executable_file_by_filename/main.go @@ -0,0 +1,7 @@ +package main + +import "fmt" + +func main() { + fmt.Println(`123`) +} \ No newline at end of file diff --git a/src/test/java/outcomes/process_executor/go/find_more_then_one_executable_file/FindMoreThenOneExecutableFile.java b/src/test/java/outcomes/process_executor/go/find_more_then_one_executable_file/FindMoreThenOneExecutableFile.java new file mode 100644 index 00000000..f7592c03 --- /dev/null +++ b/src/test/java/outcomes/process_executor/go/find_more_then_one_executable_file/FindMoreThenOneExecutableFile.java @@ -0,0 +1,30 @@ +package outcomes.process_executor.go.find_more_then_one_executable_file; + +import org.hyperskill.hstest.dynamic.DynamicTest; +import org.hyperskill.hstest.testcase.CheckResult; +import org.hyperskill.hstest.testing.TestedProgram; +import outcomes.base.ContainsMessage; +import outcomes.base.UserErrorTest; + + +public class FindMoreThenOneExecutableFile extends UserErrorTest { + + @ContainsMessage + String m1 = "Cannot decide which file to run out of the following: "; + + @ContainsMessage + String m2 = "main1.go\""; + + @ContainsMessage + String m3 = "main2.go\""; + + @ContainsMessage + String m4 = "Leave one file with this line."; + + @DynamicTest + CheckResult test() { + TestedProgram main = new TestedProgram("directory"); + return CheckResult.correct(); + } +} + diff --git a/src/test/java/outcomes/process_executor/go/find_more_then_one_executable_file/directory/main1.go b/src/test/java/outcomes/process_executor/go/find_more_then_one_executable_file/directory/main1.go new file mode 100644 index 00000000..87a1bf2b --- /dev/null +++ b/src/test/java/outcomes/process_executor/go/find_more_then_one_executable_file/directory/main1.go @@ -0,0 +1,7 @@ +package main + +import "fmt" + +func main() { + fmt.Println(`First file`) +} diff --git a/src/test/java/outcomes/process_executor/go/find_more_then_one_executable_file/directory/main2.go b/src/test/java/outcomes/process_executor/go/find_more_then_one_executable_file/directory/main2.go new file mode 100644 index 00000000..8c0ca774 --- /dev/null +++ b/src/test/java/outcomes/process_executor/go/find_more_then_one_executable_file/directory/main2.go @@ -0,0 +1,7 @@ +package main + +import "fmt" + +func main() { + fmt.Println(`Second file`) +} \ No newline at end of file diff --git a/src/test/java/outcomes/process_executor/go/find_more_then_one_executable_file/main.go b/src/test/java/outcomes/process_executor/go/find_more_then_one_executable_file/main.go new file mode 100644 index 00000000..85f0393b --- /dev/null +++ b/src/test/java/outcomes/process_executor/go/find_more_then_one_executable_file/main.go @@ -0,0 +1 @@ +package main \ No newline at end of file diff --git a/src/test/java/outcomes/process_executor/go/no_executable_files_at_directory/NoExecutableFilesAtDirectory.java b/src/test/java/outcomes/process_executor/go/no_executable_files_at_directory/NoExecutableFilesAtDirectory.java new file mode 100644 index 00000000..cd300a2b --- /dev/null +++ b/src/test/java/outcomes/process_executor/go/no_executable_files_at_directory/NoExecutableFilesAtDirectory.java @@ -0,0 +1,20 @@ +package outcomes.process_executor.go.no_executable_files_at_directory; + +import org.hyperskill.hstest.dynamic.DynamicTest; +import org.hyperskill.hstest.testcase.CheckResult; +import org.hyperskill.hstest.testing.TestedProgram; +import outcomes.base.ContainsMessage; +import outcomes.base.UserErrorTest; + + +public class NoExecutableFilesAtDirectory extends UserErrorTest { + + @ContainsMessage + String m1 = "Cannot find a file to execute your code.\nAre your project files located at"; + + @DynamicTest + CheckResult test() { + TestedProgram main = new TestedProgram("directory"); + return CheckResult.correct(); + } +} diff --git a/src/test/java/outcomes/process_executor/go/no_executable_files_at_directory/main.go b/src/test/java/outcomes/process_executor/go/no_executable_files_at_directory/main.go new file mode 100644 index 00000000..1a5c9173 --- /dev/null +++ b/src/test/java/outcomes/process_executor/go/no_executable_files_at_directory/main.go @@ -0,0 +1 @@ +package find_two_files_and_one_entry_point \ No newline at end of file diff --git a/src/test/java/outcomes/process_executor/go/no_find_executable_file_by_directory/NoFindExecutableFileByDirectory.java b/src/test/java/outcomes/process_executor/go/no_find_executable_file_by_directory/NoFindExecutableFileByDirectory.java index 1c968016..7b2ddfa0 100644 --- a/src/test/java/outcomes/process_executor/go/no_find_executable_file_by_directory/NoFindExecutableFileByDirectory.java +++ b/src/test/java/outcomes/process_executor/go/no_find_executable_file_by_directory/NoFindExecutableFileByDirectory.java @@ -1,26 +1,27 @@ package outcomes.process_executor.go.no_find_executable_file_by_directory; -import org.hyperskill.hstest.common.FileUtils; -import org.hyperskill.hstest.dynamic.input.DynamicTestingMethod; +import org.hyperskill.hstest.dynamic.DynamicTest; import org.hyperskill.hstest.testcase.CheckResult; import org.hyperskill.hstest.testing.TestedProgram; import outcomes.base.ContainsMessage; import outcomes.base.UserErrorTest; -public class NoFindExecutableFileByDirectory extends UserErrorTest { +public class NoFindExecutableFileByDirectory extends UserErrorTest { @ContainsMessage - String m = + String m1 = "Error in test #1\n" + "\n" + - "Cannot find a file to execute your code in directory \"" + FileUtils.cwd() + - "/src/test/java/outcomes/process_executor/go/no_find_executable_file_by_directory\"."; + "Cannot find a file to execute your code in directory \""; - @DynamicTestingMethod + @ContainsMessage + String m2 = "no_find_executable_file_by_directory"; + + @DynamicTest CheckResult test() { TestedProgram main = new TestedProgram( - FileUtils.cwd() + "/non_existent_directory"); + "outcomes.process_executor.go.non_existent_directory"); return CheckResult.correct(); } diff --git a/src/test/java/outcomes/process_executor/go/no_find_executable_file_by_directory/main.go b/src/test/java/outcomes/process_executor/go/no_find_executable_file_by_directory/main.go index 1e9375ad..85f0393b 100644 --- a/src/test/java/outcomes/process_executor/go/no_find_executable_file_by_directory/main.go +++ b/src/test/java/outcomes/process_executor/go/no_find_executable_file_by_directory/main.go @@ -1,9 +1 @@ -package main - -import ( - "fmt" -) - -func main() { - Println(`12123123`) -} \ No newline at end of file +package main \ No newline at end of file diff --git a/src/test/java/outcomes/process_executor/go/no_find_executable_file_by_file_path/NoFindExecutableFileByFilePath.java b/src/test/java/outcomes/process_executor/go/no_find_executable_file_by_file_path/NoFindExecutableFileByFilePath.java index 5039d3ef..b5fbfbd5 100644 --- a/src/test/java/outcomes/process_executor/go/no_find_executable_file_by_file_path/NoFindExecutableFileByFilePath.java +++ b/src/test/java/outcomes/process_executor/go/no_find_executable_file_by_file_path/NoFindExecutableFileByFilePath.java @@ -1,7 +1,6 @@ package outcomes.process_executor.go.no_find_executable_file_by_file_path; -import org.hyperskill.hstest.common.FileUtils; -import org.hyperskill.hstest.dynamic.input.DynamicTestingMethod; +import org.hyperskill.hstest.dynamic.DynamicTest; import org.hyperskill.hstest.testcase.CheckResult; import org.hyperskill.hstest.testing.TestedProgram; import outcomes.base.ContainsMessage; @@ -11,16 +10,17 @@ public class NoFindExecutableFileByFilePath extends UserErrorTest { @ContainsMessage - String m = + String m1 = "Error in test #1\n" + "\n" + - "Cannot find a file to execute your code in directory \"" + FileUtils.cwd() + - "/src/test/java/outcomes/process_executor/go/no_find_executable_file_by_file_path\"."; + "Cannot find a file to execute your code in directory \""; - @DynamicTestingMethod + @ContainsMessage + String m2 = "no_find_executable_file_by_file_path"; + + @DynamicTest CheckResult test() { - TestedProgram main = new TestedProgram( - FileUtils.cwd() + "/non_existent_file.go"); + TestedProgram main = new TestedProgram("outcomes.process_executor.go.non_existent_file.go"); return CheckResult.correct(); } diff --git a/src/test/java/outcomes/process_executor/go/no_find_executable_file_by_file_path/main.go b/src/test/java/outcomes/process_executor/go/no_find_executable_file_by_file_path/main.go index 1e9375ad..85f0393b 100644 --- a/src/test/java/outcomes/process_executor/go/no_find_executable_file_by_file_path/main.go +++ b/src/test/java/outcomes/process_executor/go/no_find_executable_file_by_file_path/main.go @@ -1,9 +1 @@ -package main - -import ( - "fmt" -) - -func main() { - Println(`12123123`) -} \ No newline at end of file +package main \ No newline at end of file diff --git a/src/test/java/outcomes/separate_package/fallback_to_class_package/FallbackToClassPackage.java b/src/test/java/outcomes/separate_package/fallback_to_class_package/FallbackToClassPackage.java deleted file mode 100644 index 091e5d32..00000000 --- a/src/test/java/outcomes/separate_package/fallback_to_class_package/FallbackToClassPackage.java +++ /dev/null @@ -1,32 +0,0 @@ -package outcomes.separate_package.fallback_to_class_package; - -import org.hyperskill.hstest.stage.StageTest; -import org.hyperskill.hstest.testcase.CheckResult; -import org.hyperskill.hstest.testcase.TestCase; - -import java.util.Arrays; -import java.util.List; - -class ClassWithoutMainMethod { - -} - -public class FallbackToClassPackage extends StageTest { - - public FallbackToClassPackage() { - super(FallbackToClassPackage.class); - } - - @Override - public List generate() { - return Arrays.asList( - new TestCase() - ); - } - - @Override - public CheckResult check(String reply, Object attach) { - return CheckResult.correct(); - } - -} diff --git a/src/test/java/outcomes/separate_package/no_class_by_class_name/NoClassByClassName.java b/src/test/java/outcomes/separate_package/no_class_by_class_name/NoClassByClassName.java index 9ca69f66..f20ecdb8 100644 --- a/src/test/java/outcomes/separate_package/no_class_by_class_name/NoClassByClassName.java +++ b/src/test/java/outcomes/separate_package/no_class_by_class_name/NoClassByClassName.java @@ -1,6 +1,6 @@ package outcomes.separate_package.no_class_by_class_name; -import org.hyperskill.hstest.dynamic.input.DynamicTestingMethod; +import org.hyperskill.hstest.dynamic.DynamicTest; import org.hyperskill.hstest.testcase.CheckResult; import org.hyperskill.hstest.testing.TestedProgram; import outcomes.base.ContainsMessage; @@ -16,7 +16,7 @@ public class NoClassByClassName extends UserErrorTest { "Cannot find a class with a main method.\n" + "Check if you declared it as \"public static void main(String[] args)\"."; - @DynamicTestingMethod + @DynamicTest CheckResult test() { TestedProgram main = new TestedProgram( "outcomes.separate_package.no_class_by_class_name.NonExistentClass"); diff --git a/src/test/java/outcomes/separate_package/fallback_to_class_package/Main.java b/src/test/java/outcomes/separate_package/no_fallback_to_class_package/Main.java similarity index 65% rename from src/test/java/outcomes/separate_package/fallback_to_class_package/Main.java rename to src/test/java/outcomes/separate_package/no_fallback_to_class_package/Main.java index e29d593c..11edb970 100644 --- a/src/test/java/outcomes/separate_package/fallback_to_class_package/Main.java +++ b/src/test/java/outcomes/separate_package/no_fallback_to_class_package/Main.java @@ -1,4 +1,4 @@ -package outcomes.separate_package.fallback_to_class_package; +package outcomes.separate_package.no_fallback_to_class_package; class Main { public static void main(String[] args) { diff --git a/src/test/java/outcomes/separate_package/no_fallback_to_class_package/NoFallbackToClassPackage.java b/src/test/java/outcomes/separate_package/no_fallback_to_class_package/NoFallbackToClassPackage.java new file mode 100644 index 00000000..404acf12 --- /dev/null +++ b/src/test/java/outcomes/separate_package/no_fallback_to_class_package/NoFallbackToClassPackage.java @@ -0,0 +1,34 @@ +package outcomes.separate_package.no_fallback_to_class_package; + +import org.hyperskill.hstest.testcase.CheckResult; +import org.hyperskill.hstest.testcase.TestCase; +import org.hyperskill.hstest.testing.TestedProgram; +import outcomes.base.ContainsMessage; +import outcomes.base.UserErrorTest; + +import java.util.Arrays; +import java.util.List; + +class ClassWithoutMainMethod { + +} + +public class NoFallbackToClassPackage extends UserErrorTest { + + @ContainsMessage + String m1 = "Error in test #1\n" + + "\n" + + "Cannot find a class with a main method in package " + + "\"outcomes.separate_package.no_fallback_to_class_package\".\n" + + "Check if you declared it as \"public static void main(String[] args)\"."; + + @Override + public List> generate() { + return Arrays.asList( + new TestCase().setDynamicTesting(() -> { + TestedProgram main = new TestedProgram(ClassWithoutMainMethod.class); + return CheckResult.correct(); + }) + ); + } +} diff --git a/src/test/java/outcomes/separate_package/no_package_by_package_name/NoPackageByPackageName.java b/src/test/java/outcomes/separate_package/no_package_by_package_name/NoPackageByPackageName.java index 6a7ae08f..4d2837db 100644 --- a/src/test/java/outcomes/separate_package/no_package_by_package_name/NoPackageByPackageName.java +++ b/src/test/java/outcomes/separate_package/no_package_by_package_name/NoPackageByPackageName.java @@ -1,6 +1,6 @@ package outcomes.separate_package.no_package_by_package_name; -import org.hyperskill.hstest.dynamic.input.DynamicTestingMethod; +import org.hyperskill.hstest.dynamic.DynamicTest; import org.hyperskill.hstest.testcase.CheckResult; import org.hyperskill.hstest.testing.TestedProgram; import outcomes.base.ContainsMessage; @@ -10,13 +10,13 @@ public class NoPackageByPackageName extends UserErrorTest { @ContainsMessage - String m = + String m1 = "Error in test #1\n" + "\n" + - "Cannot find a class with a main method in package \"outcomes.separate_package.non_existent_package\".\n" + + "Cannot find a class with a main method.\n" + "Check if you declared it as \"public static void main(String[] args)\"."; - @DynamicTestingMethod + @DynamicTest CheckResult test() { TestedProgram main = new TestedProgram( "outcomes.separate_package.non_existent_package"); From b9ff104a7912500ed5e343360c61c5cbaa0cd0a0 Mon Sep 17 00:00:00 2001 From: tanya Date: Wed, 13 Jul 2022 16:46:25 +0300 Subject: [PATCH 46/55] Add stopProcessTest function to process executor tests --- .../FindExecutableFileByDirectory.java | 28 ++++++++++++++---- .../FindExecutableFileByFileName.java | 9 ++++++ .../FindMoreThenOneExecutableFile.java | 9 ++++++ .../NoExecutableFilesAtDirectory.java | 9 ++++++ .../NoFindExecutableFileByDirectory.java | 29 ++++++++++++++----- .../NoFindExecutableFileByFilePath.java | 9 ++++++ 6 files changed, 80 insertions(+), 13 deletions(-) diff --git a/src/test/java/outcomes/process_executor/go/find_executable_file_by_directory/FindExecutableFileByDirectory.java b/src/test/java/outcomes/process_executor/go/find_executable_file_by_directory/FindExecutableFileByDirectory.java index 5e3f4b58..483763fb 100644 --- a/src/test/java/outcomes/process_executor/go/find_executable_file_by_directory/FindExecutableFileByDirectory.java +++ b/src/test/java/outcomes/process_executor/go/find_executable_file_by_directory/FindExecutableFileByDirectory.java @@ -1,17 +1,33 @@ package outcomes.process_executor.go.find_executable_file_by_directory; -import org.hyperskill.hstest.dynamic.DynamicTest; import org.hyperskill.hstest.stage.StageTest; import org.hyperskill.hstest.testcase.CheckResult; +import org.hyperskill.hstest.testcase.TestCase; import org.hyperskill.hstest.testing.TestedProgram; +import org.junit.Assume; +import org.junit.BeforeClass; + +import java.util.Arrays; +import java.util.List; + +import static org.hyperskill.hstest.testing.ExecutionOptions.includeProcessTesting; public class FindExecutableFileByDirectory extends StageTest { - @DynamicTest - CheckResult test() { - TestedProgram main = new TestedProgram("directory_to_find"); - String out = main.start(); - return new CheckResult("321\n".equals(out), ""); + @BeforeClass + public static void stopProcessTest() { + Assume.assumeTrue(includeProcessTesting); + } + + @Override + public List> generate() { + return Arrays.asList( + new TestCase().setDynamicTesting(() -> { + TestedProgram main = new TestedProgram("directory_to_find"); + String out = main.start(); + return new CheckResult("321\n".equals(out), ""); + }) + ); } } diff --git a/src/test/java/outcomes/process_executor/go/find_executable_file_by_filename/FindExecutableFileByFileName.java b/src/test/java/outcomes/process_executor/go/find_executable_file_by_filename/FindExecutableFileByFileName.java index a66e66d0..351efd9e 100644 --- a/src/test/java/outcomes/process_executor/go/find_executable_file_by_filename/FindExecutableFileByFileName.java +++ b/src/test/java/outcomes/process_executor/go/find_executable_file_by_filename/FindExecutableFileByFileName.java @@ -4,10 +4,19 @@ import org.hyperskill.hstest.stage.StageTest; import org.hyperskill.hstest.testcase.CheckResult; import org.hyperskill.hstest.testing.TestedProgram; +import org.junit.Assume; +import org.junit.BeforeClass; + +import static org.hyperskill.hstest.testing.ExecutionOptions.includeProcessTesting; public class FindExecutableFileByFileName extends StageTest { + @BeforeClass + public static void stopProcessTest() { + Assume.assumeTrue(includeProcessTesting); + } + @DynamicTest CheckResult test() { TestedProgram main = new TestedProgram("main.go"); diff --git a/src/test/java/outcomes/process_executor/go/find_more_then_one_executable_file/FindMoreThenOneExecutableFile.java b/src/test/java/outcomes/process_executor/go/find_more_then_one_executable_file/FindMoreThenOneExecutableFile.java index f7592c03..afa3ffe4 100644 --- a/src/test/java/outcomes/process_executor/go/find_more_then_one_executable_file/FindMoreThenOneExecutableFile.java +++ b/src/test/java/outcomes/process_executor/go/find_more_then_one_executable_file/FindMoreThenOneExecutableFile.java @@ -3,12 +3,21 @@ import org.hyperskill.hstest.dynamic.DynamicTest; import org.hyperskill.hstest.testcase.CheckResult; import org.hyperskill.hstest.testing.TestedProgram; +import org.junit.Assume; +import org.junit.BeforeClass; import outcomes.base.ContainsMessage; import outcomes.base.UserErrorTest; +import static org.hyperskill.hstest.testing.ExecutionOptions.includeProcessTesting; + public class FindMoreThenOneExecutableFile extends UserErrorTest { + @BeforeClass + public static void stopProcessTest() { + Assume.assumeTrue(includeProcessTesting); + } + @ContainsMessage String m1 = "Cannot decide which file to run out of the following: "; diff --git a/src/test/java/outcomes/process_executor/go/no_executable_files_at_directory/NoExecutableFilesAtDirectory.java b/src/test/java/outcomes/process_executor/go/no_executable_files_at_directory/NoExecutableFilesAtDirectory.java index cd300a2b..b984a13a 100644 --- a/src/test/java/outcomes/process_executor/go/no_executable_files_at_directory/NoExecutableFilesAtDirectory.java +++ b/src/test/java/outcomes/process_executor/go/no_executable_files_at_directory/NoExecutableFilesAtDirectory.java @@ -3,12 +3,21 @@ import org.hyperskill.hstest.dynamic.DynamicTest; import org.hyperskill.hstest.testcase.CheckResult; import org.hyperskill.hstest.testing.TestedProgram; +import org.junit.Assume; +import org.junit.BeforeClass; import outcomes.base.ContainsMessage; import outcomes.base.UserErrorTest; +import static org.hyperskill.hstest.testing.ExecutionOptions.includeProcessTesting; + public class NoExecutableFilesAtDirectory extends UserErrorTest { + @BeforeClass + public static void stopProcessTest() { + Assume.assumeTrue(includeProcessTesting); + } + @ContainsMessage String m1 = "Cannot find a file to execute your code.\nAre your project files located at"; diff --git a/src/test/java/outcomes/process_executor/go/no_find_executable_file_by_directory/NoFindExecutableFileByDirectory.java b/src/test/java/outcomes/process_executor/go/no_find_executable_file_by_directory/NoFindExecutableFileByDirectory.java index 7b2ddfa0..c5bd53eb 100644 --- a/src/test/java/outcomes/process_executor/go/no_find_executable_file_by_directory/NoFindExecutableFileByDirectory.java +++ b/src/test/java/outcomes/process_executor/go/no_find_executable_file_by_directory/NoFindExecutableFileByDirectory.java @@ -1,14 +1,26 @@ package outcomes.process_executor.go.no_find_executable_file_by_directory; -import org.hyperskill.hstest.dynamic.DynamicTest; import org.hyperskill.hstest.testcase.CheckResult; +import org.hyperskill.hstest.testcase.TestCase; import org.hyperskill.hstest.testing.TestedProgram; +import org.junit.Assume; +import org.junit.BeforeClass; import outcomes.base.ContainsMessage; import outcomes.base.UserErrorTest; +import java.util.Collections; +import java.util.List; + +import static org.hyperskill.hstest.testing.ExecutionOptions.includeProcessTesting; + public class NoFindExecutableFileByDirectory extends UserErrorTest { + @BeforeClass + public static void stopProcessTest() { + Assume.assumeTrue(includeProcessTesting); + } + @ContainsMessage String m1 = "Error in test #1\n" + @@ -18,11 +30,14 @@ public class NoFindExecutableFileByDirectory extends UserErrorTest { @ContainsMessage String m2 = "no_find_executable_file_by_directory"; - @DynamicTest - CheckResult test() { - TestedProgram main = new TestedProgram( - "outcomes.process_executor.go.non_existent_directory"); - return CheckResult.correct(); + @Override + public List> generate() { + return Collections.singletonList( + new TestCase().setDynamicTesting(() -> { + TestedProgram main = new TestedProgram( + "outcomes.process_executor.go.non_existent_directory"); + return CheckResult.correct(); + }) + ); } - } diff --git a/src/test/java/outcomes/process_executor/go/no_find_executable_file_by_file_path/NoFindExecutableFileByFilePath.java b/src/test/java/outcomes/process_executor/go/no_find_executable_file_by_file_path/NoFindExecutableFileByFilePath.java index b5fbfbd5..f3a9ea82 100644 --- a/src/test/java/outcomes/process_executor/go/no_find_executable_file_by_file_path/NoFindExecutableFileByFilePath.java +++ b/src/test/java/outcomes/process_executor/go/no_find_executable_file_by_file_path/NoFindExecutableFileByFilePath.java @@ -3,12 +3,21 @@ import org.hyperskill.hstest.dynamic.DynamicTest; import org.hyperskill.hstest.testcase.CheckResult; import org.hyperskill.hstest.testing.TestedProgram; +import org.junit.Assume; +import org.junit.BeforeClass; import outcomes.base.ContainsMessage; import outcomes.base.UserErrorTest; +import static org.hyperskill.hstest.testing.ExecutionOptions.includeProcessTesting; + public class NoFindExecutableFileByFilePath extends UserErrorTest { + @BeforeClass + public static void stopProcessTest() { + Assume.assumeTrue(includeProcessTesting); + } + @ContainsMessage String m1 = "Error in test #1\n" + From a68bc702ab27a716f2b6821423d79786965cc1e3 Mon Sep 17 00:00:00 2001 From: tanya Date: Wed, 13 Jul 2022 19:36:48 +0300 Subject: [PATCH 47/55] Code review fixes --- .../testing/execution/MainMethodExecutor.java | 40 +++++++------------ .../find_executable_file_by_directory/main.go | 8 +++- .../FindMoreThanOneExecutableFile.java} | 8 +++- .../directory/main1.go | 0 .../directory/main2.go | 0 .../main.go | 7 ++++ .../main.go | 1 - .../NoExecutableFilesAtDirectory.java | 8 +++- .../no_executable_files_at_directory/main.go | 8 +++- .../main.go | 8 +++- .../main.go | 8 +++- .../NoClassByClassName.java | 6 +-- .../NoFallbackToClassPackage.java | 4 +- .../no_main_method/NoMainMethodFound.java | 2 +- .../NoMainMethodByName.java | 2 +- .../NoPackageByPackageName.java | 6 +-- .../not_public_1/MainMethodNotPublic1.java | 9 +++-- .../not_public_2/MainMethodNotPublic2.java | 9 +++-- .../not_public_3/MainMethodNotPublic3.java | 9 +++-- .../not_static/MainMethodNotStatic.java | 9 +++-- 20 files changed, 93 insertions(+), 59 deletions(-) rename src/test/java/outcomes/process_executor/go/{find_more_then_one_executable_file/FindMoreThenOneExecutableFile.java => find_more_than_one_executable_file/FindMoreThanOneExecutableFile.java} (80%) rename src/test/java/outcomes/process_executor/go/{find_more_then_one_executable_file => find_more_than_one_executable_file}/directory/main1.go (100%) rename src/test/java/outcomes/process_executor/go/{find_more_then_one_executable_file => find_more_than_one_executable_file}/directory/main2.go (100%) create mode 100644 src/test/java/outcomes/process_executor/go/find_more_than_one_executable_file/main.go delete mode 100644 src/test/java/outcomes/process_executor/go/find_more_then_one_executable_file/main.go diff --git a/src/main/java/org/hyperskill/hstest/testing/execution/MainMethodExecutor.java b/src/main/java/org/hyperskill/hstest/testing/execution/MainMethodExecutor.java index 1ace6858..99db73cf 100644 --- a/src/main/java/org/hyperskill/hstest/testing/execution/MainMethodExecutor.java +++ b/src/main/java/org/hyperskill/hstest/testing/execution/MainMethodExecutor.java @@ -58,8 +58,9 @@ public MainMethodExecutor(String sourceName) { private void initByClassInstance(Class clazz) { if (!ReflectionUtils.hasMainMethod(clazz)) { - String errorMessage = getNotFoundClassWithMainMethodMessage(clazz.getPackage().getName()); - throw new ErrorWithFeedback(errorMessage); + throw new ErrorWithFeedback( + "Cannot find a main method in class \"" + clazz.getName() + "\".\n" + + "Check if you declared it as \"public static void main(String[] args)\"."); } runClass = clazz; @@ -74,7 +75,7 @@ private void initByName(String sourceName) { } private void initByPackageName(String packageName) { - initByNothing(packageName, false); + initByNothing(packageName); } private void initByClassName(String className) { @@ -82,8 +83,9 @@ private void initByClassName(String className) { Class clazz = Thread.currentThread().getContextClassLoader().loadClass(className); initByClassInstance(clazz); } catch (ClassNotFoundException | NoClassDefFoundError ex) { - String errorMessage = getNotFoundClassWithMainMethodMessage(""); - throw new ErrorWithFeedback(errorMessage); + throw new ErrorWithFeedback( + "Cannot find either a package or a class named \"" + className + "\". " + + "Check if you've created one of these."); } } @@ -92,10 +94,6 @@ private void initByNothing() { } private void initByNothing(String userPackage) { - initByNothing(userPackage, true); - } - - private void initByNothing(String userPackage, boolean tryEmptyPackage) { List> classesWithMainMethod = ReflectionUtils .getAllClassesFromPackage(userPackage) .stream() @@ -104,32 +102,24 @@ private void initByNothing(String userPackage, boolean tryEmptyPackage) { int count = classesWithMainMethod.size(); + String inPackage = ""; + if (!userPackage.isEmpty()) { + inPackage = " in package \"" + userPackage + "\""; + } + if (count == 0) { - if (tryEmptyPackage) { - initByNothing("", false); - return; - } - String errorMessage = getNotFoundClassWithMainMethodMessage(userPackage); - throw new ErrorWithFeedback(errorMessage); + throw new ErrorWithFeedback( + "Cannot find a class with a main method" + inPackage + ".\n" + + "Check if you declared it as \"public static void main(String[] args)\"."); } if (count > 1) { - if (tryEmptyPackage) { - initByNothing("", false); - return; - } - String allClassesNames = classesWithMainMethod .stream() .map(Class::getName) .sorted() .collect(joining(", ")); - String inPackage = ""; - if (!userPackage.isEmpty()) { - inPackage = " in package \"" + userPackage + "\""; - } - throw new ErrorWithFeedback( "There are " + count + " classes with main method" + inPackage + ": " + allClassesNames + ".\n" diff --git a/src/test/java/outcomes/process_executor/go/find_executable_file_by_directory/main.go b/src/test/java/outcomes/process_executor/go/find_executable_file_by_directory/main.go index 85f0393b..8571a2be 100644 --- a/src/test/java/outcomes/process_executor/go/find_executable_file_by_directory/main.go +++ b/src/test/java/outcomes/process_executor/go/find_executable_file_by_directory/main.go @@ -1 +1,7 @@ -package main \ No newline at end of file +package main + +import "fmt" + +func main() { + fmt.Println(`Main file`) +} \ No newline at end of file diff --git a/src/test/java/outcomes/process_executor/go/find_more_then_one_executable_file/FindMoreThenOneExecutableFile.java b/src/test/java/outcomes/process_executor/go/find_more_than_one_executable_file/FindMoreThanOneExecutableFile.java similarity index 80% rename from src/test/java/outcomes/process_executor/go/find_more_then_one_executable_file/FindMoreThenOneExecutableFile.java rename to src/test/java/outcomes/process_executor/go/find_more_than_one_executable_file/FindMoreThanOneExecutableFile.java index afa3ffe4..0763955f 100644 --- a/src/test/java/outcomes/process_executor/go/find_more_then_one_executable_file/FindMoreThenOneExecutableFile.java +++ b/src/test/java/outcomes/process_executor/go/find_more_than_one_executable_file/FindMoreThanOneExecutableFile.java @@ -1,4 +1,4 @@ -package outcomes.process_executor.go.find_more_then_one_executable_file; +package outcomes.process_executor.go.find_more_than_one_executable_file; import org.hyperskill.hstest.dynamic.DynamicTest; import org.hyperskill.hstest.testcase.CheckResult; @@ -6,12 +6,13 @@ import org.junit.Assume; import org.junit.BeforeClass; import outcomes.base.ContainsMessage; +import outcomes.base.NotContainMessage; import outcomes.base.UserErrorTest; import static org.hyperskill.hstest.testing.ExecutionOptions.includeProcessTesting; -public class FindMoreThenOneExecutableFile extends UserErrorTest { +public class FindMoreThanOneExecutableFile extends UserErrorTest { @BeforeClass public static void stopProcessTest() { @@ -30,6 +31,9 @@ public static void stopProcessTest() { @ContainsMessage String m4 = "Leave one file with this line."; + @NotContainMessage + String m5 = "main.go"; + @DynamicTest CheckResult test() { TestedProgram main = new TestedProgram("directory"); diff --git a/src/test/java/outcomes/process_executor/go/find_more_then_one_executable_file/directory/main1.go b/src/test/java/outcomes/process_executor/go/find_more_than_one_executable_file/directory/main1.go similarity index 100% rename from src/test/java/outcomes/process_executor/go/find_more_then_one_executable_file/directory/main1.go rename to src/test/java/outcomes/process_executor/go/find_more_than_one_executable_file/directory/main1.go diff --git a/src/test/java/outcomes/process_executor/go/find_more_then_one_executable_file/directory/main2.go b/src/test/java/outcomes/process_executor/go/find_more_than_one_executable_file/directory/main2.go similarity index 100% rename from src/test/java/outcomes/process_executor/go/find_more_then_one_executable_file/directory/main2.go rename to src/test/java/outcomes/process_executor/go/find_more_than_one_executable_file/directory/main2.go diff --git a/src/test/java/outcomes/process_executor/go/find_more_than_one_executable_file/main.go b/src/test/java/outcomes/process_executor/go/find_more_than_one_executable_file/main.go new file mode 100644 index 00000000..8571a2be --- /dev/null +++ b/src/test/java/outcomes/process_executor/go/find_more_than_one_executable_file/main.go @@ -0,0 +1,7 @@ +package main + +import "fmt" + +func main() { + fmt.Println(`Main file`) +} \ No newline at end of file diff --git a/src/test/java/outcomes/process_executor/go/find_more_then_one_executable_file/main.go b/src/test/java/outcomes/process_executor/go/find_more_then_one_executable_file/main.go deleted file mode 100644 index 85f0393b..00000000 --- a/src/test/java/outcomes/process_executor/go/find_more_then_one_executable_file/main.go +++ /dev/null @@ -1 +0,0 @@ -package main \ No newline at end of file diff --git a/src/test/java/outcomes/process_executor/go/no_executable_files_at_directory/NoExecutableFilesAtDirectory.java b/src/test/java/outcomes/process_executor/go/no_executable_files_at_directory/NoExecutableFilesAtDirectory.java index b984a13a..7cefba8c 100644 --- a/src/test/java/outcomes/process_executor/go/no_executable_files_at_directory/NoExecutableFilesAtDirectory.java +++ b/src/test/java/outcomes/process_executor/go/no_executable_files_at_directory/NoExecutableFilesAtDirectory.java @@ -21,9 +21,15 @@ public static void stopProcessTest() { @ContainsMessage String m1 = "Cannot find a file to execute your code.\nAre your project files located at"; + @ContainsMessage + String m2 = "no_executable_files_at_directory"; + + @ContainsMessage + String m3 = "go_example_project"; + @DynamicTest CheckResult test() { - TestedProgram main = new TestedProgram("directory"); + TestedProgram main = new TestedProgram("go_example_project"); return CheckResult.correct(); } } diff --git a/src/test/java/outcomes/process_executor/go/no_executable_files_at_directory/main.go b/src/test/java/outcomes/process_executor/go/no_executable_files_at_directory/main.go index 1a5c9173..8571a2be 100644 --- a/src/test/java/outcomes/process_executor/go/no_executable_files_at_directory/main.go +++ b/src/test/java/outcomes/process_executor/go/no_executable_files_at_directory/main.go @@ -1 +1,7 @@ -package find_two_files_and_one_entry_point \ No newline at end of file +package main + +import "fmt" + +func main() { + fmt.Println(`Main file`) +} \ No newline at end of file diff --git a/src/test/java/outcomes/process_executor/go/no_find_executable_file_by_directory/main.go b/src/test/java/outcomes/process_executor/go/no_find_executable_file_by_directory/main.go index 85f0393b..8571a2be 100644 --- a/src/test/java/outcomes/process_executor/go/no_find_executable_file_by_directory/main.go +++ b/src/test/java/outcomes/process_executor/go/no_find_executable_file_by_directory/main.go @@ -1 +1,7 @@ -package main \ No newline at end of file +package main + +import "fmt" + +func main() { + fmt.Println(`Main file`) +} \ No newline at end of file diff --git a/src/test/java/outcomes/process_executor/go/no_find_executable_file_by_file_path/main.go b/src/test/java/outcomes/process_executor/go/no_find_executable_file_by_file_path/main.go index 85f0393b..8571a2be 100644 --- a/src/test/java/outcomes/process_executor/go/no_find_executable_file_by_file_path/main.go +++ b/src/test/java/outcomes/process_executor/go/no_find_executable_file_by_file_path/main.go @@ -1 +1,7 @@ -package main \ No newline at end of file +package main + +import "fmt" + +func main() { + fmt.Println(`Main file`) +} \ No newline at end of file diff --git a/src/test/java/outcomes/separate_package/no_class_by_class_name/NoClassByClassName.java b/src/test/java/outcomes/separate_package/no_class_by_class_name/NoClassByClassName.java index f20ecdb8..cec038e9 100644 --- a/src/test/java/outcomes/separate_package/no_class_by_class_name/NoClassByClassName.java +++ b/src/test/java/outcomes/separate_package/no_class_by_class_name/NoClassByClassName.java @@ -12,9 +12,9 @@ public class NoClassByClassName extends UserErrorTest { @ContainsMessage String m = "Error in test #1\n" + - "\n" + - "Cannot find a class with a main method.\n" + - "Check if you declared it as \"public static void main(String[] args)\"."; + "\n" + + "Cannot find either a package or a class named \"outcomes.separate_package.no_class_by_class_name.NonExistentClass\". " + + "Check if you've created one of these."; @DynamicTest CheckResult test() { diff --git a/src/test/java/outcomes/separate_package/no_fallback_to_class_package/NoFallbackToClassPackage.java b/src/test/java/outcomes/separate_package/no_fallback_to_class_package/NoFallbackToClassPackage.java index 404acf12..ceb6add2 100644 --- a/src/test/java/outcomes/separate_package/no_fallback_to_class_package/NoFallbackToClassPackage.java +++ b/src/test/java/outcomes/separate_package/no_fallback_to_class_package/NoFallbackToClassPackage.java @@ -18,8 +18,8 @@ public class NoFallbackToClassPackage extends UserErrorTest { @ContainsMessage String m1 = "Error in test #1\n" + "\n" + - "Cannot find a class with a main method in package " + - "\"outcomes.separate_package.no_fallback_to_class_package\".\n" + + "Cannot find a main method in class " + + "\"outcomes.separate_package.no_fallback_to_class_package.ClassWithoutMainMethod\".\n" + "Check if you declared it as \"public static void main(String[] args)\"."; @Override diff --git a/src/test/java/outcomes/separate_package/no_main_method/NoMainMethodFound.java b/src/test/java/outcomes/separate_package/no_main_method/NoMainMethodFound.java index 8bc063ba..4fee6a51 100644 --- a/src/test/java/outcomes/separate_package/no_main_method/NoMainMethodFound.java +++ b/src/test/java/outcomes/separate_package/no_main_method/NoMainMethodFound.java @@ -18,7 +18,7 @@ public class NoMainMethodFound extends UserErrorTest { String m = "Error in test #1\n" + "\n" + - "Cannot find a class with a main method in package \"outcomes.separate_package.no_main_method\".\n" + + "Cannot find a main method in class \"outcomes.separate_package.no_main_method.NoMainMethodFoundMain\".\n" + "Check if you declared it as \"public static void main(String[] args)\"."; public NoMainMethodFound() { diff --git a/src/test/java/outcomes/separate_package/no_main_method_by_name/NoMainMethodByName.java b/src/test/java/outcomes/separate_package/no_main_method_by_name/NoMainMethodByName.java index 667c4052..cac411ab 100644 --- a/src/test/java/outcomes/separate_package/no_main_method_by_name/NoMainMethodByName.java +++ b/src/test/java/outcomes/separate_package/no_main_method_by_name/NoMainMethodByName.java @@ -16,7 +16,7 @@ public class NoMainMethodByName extends UserErrorTest { String m = "Error in test #1\n" + "\n" + - "Cannot find a class with a main method in package \"outcomes.separate_package.no_main_method_by_name\".\n" + + "Cannot find a main method in class \"outcomes.separate_package.no_main_method_by_name.NoMainMethodByNameMain\".\n" + "Check if you declared it as \"public static void main(String[] args)\"."; @DynamicTestingMethod diff --git a/src/test/java/outcomes/separate_package/no_package_by_package_name/NoPackageByPackageName.java b/src/test/java/outcomes/separate_package/no_package_by_package_name/NoPackageByPackageName.java index 4d2837db..62bd9d8a 100644 --- a/src/test/java/outcomes/separate_package/no_package_by_package_name/NoPackageByPackageName.java +++ b/src/test/java/outcomes/separate_package/no_package_by_package_name/NoPackageByPackageName.java @@ -12,9 +12,9 @@ public class NoPackageByPackageName extends UserErrorTest { @ContainsMessage String m1 = "Error in test #1\n" + - "\n" + - "Cannot find a class with a main method.\n" + - "Check if you declared it as \"public static void main(String[] args)\"."; + "\n" + + "Cannot find either a package or a class named \"outcomes.separate_package.non_existent_package\". " + + "Check if you've created one of these."; @DynamicTest CheckResult test() { diff --git a/src/test/java/outcomes/separate_package/not_public_1/MainMethodNotPublic1.java b/src/test/java/outcomes/separate_package/not_public_1/MainMethodNotPublic1.java index 0c89b5d2..2906d8f6 100644 --- a/src/test/java/outcomes/separate_package/not_public_1/MainMethodNotPublic1.java +++ b/src/test/java/outcomes/separate_package/not_public_1/MainMethodNotPublic1.java @@ -18,10 +18,11 @@ public class MainMethodNotPublic1 extends UserErrorTest { @ContainsMessage String m = - "Error in test #1\n" + - "\n" + - "Cannot find a class with a main method in package \"outcomes.separate_package.not_public_1\".\n" + - "Check if you declared it as \"public static void main(String[] args)\"."; + "Error in test #1\n" + + "\n" + + "Cannot find a main method in class " + + "\"outcomes.separate_package.not_public_1.MainMethodNotPublic1Main\".\n" + + "Check if you declared it as \"public static void main(String[] args)\"."; public MainMethodNotPublic1() { super(MainMethodNotPublic1Main.class); diff --git a/src/test/java/outcomes/separate_package/not_public_2/MainMethodNotPublic2.java b/src/test/java/outcomes/separate_package/not_public_2/MainMethodNotPublic2.java index 5cd760f9..8350f7e0 100644 --- a/src/test/java/outcomes/separate_package/not_public_2/MainMethodNotPublic2.java +++ b/src/test/java/outcomes/separate_package/not_public_2/MainMethodNotPublic2.java @@ -18,10 +18,11 @@ public class MainMethodNotPublic2 extends UserErrorTest { @ContainsMessage String m = - "Error in test #1\n" + - "\n" + - "Cannot find a class with a main method in package \"outcomes.separate_package.not_public_2\".\n" + - "Check if you declared it as \"public static void main(String[] args)\"."; + "Error in test #1\n" + + "\n" + + "Cannot find a main method in class " + + "\"outcomes.separate_package.not_public_2.MainMethodNotPublic2Main\".\n" + + "Check if you declared it as \"public static void main(String[] args)\"."; public MainMethodNotPublic2() { super(MainMethodNotPublic2Main.class); diff --git a/src/test/java/outcomes/separate_package/not_public_3/MainMethodNotPublic3.java b/src/test/java/outcomes/separate_package/not_public_3/MainMethodNotPublic3.java index 80fc64cd..30d73e04 100644 --- a/src/test/java/outcomes/separate_package/not_public_3/MainMethodNotPublic3.java +++ b/src/test/java/outcomes/separate_package/not_public_3/MainMethodNotPublic3.java @@ -18,10 +18,11 @@ public class MainMethodNotPublic3 extends UserErrorTest { @ContainsMessage String m = - "Error in test #1\n" + - "\n" + - "Cannot find a class with a main method in package \"outcomes.separate_package.not_public_3\".\n" + - "Check if you declared it as \"public static void main(String[] args)\"."; + "Error in test #1\n" + + "\n" + + "Cannot find a main method in class " + + "\"outcomes.separate_package.not_public_3.MainMethodNotPublic3Main\".\n" + + "Check if you declared it as \"public static void main(String[] args)\"."; public MainMethodNotPublic3() { super(MainMethodNotPublic3Main.class); diff --git a/src/test/java/outcomes/separate_package/not_static/MainMethodNotStatic.java b/src/test/java/outcomes/separate_package/not_static/MainMethodNotStatic.java index 59dae457..4bf2601b 100644 --- a/src/test/java/outcomes/separate_package/not_static/MainMethodNotStatic.java +++ b/src/test/java/outcomes/separate_package/not_static/MainMethodNotStatic.java @@ -18,10 +18,11 @@ public class MainMethodNotStatic extends UserErrorTest { @ContainsMessage String m = - "Error in test #1\n" + - "\n" + - "Cannot find a class with a main method in package \"outcomes.separate_package.not_static\".\n" + - "Check if you declared it as \"public static void main(String[] args)\"."; + "Error in test #1\n" + + "\n" + + "Cannot find a main method in class " + + "\"outcomes.separate_package.not_static.MainMethodNotStaticMain\".\n" + + "Check if you declared it as \"public static void main(String[] args)\"."; public MainMethodNotStatic() { super(MainMethodNotStaticMain.class); From 1690b6635a0cdf6031aa37a299a4b8659be72fd8 Mon Sep 17 00:00:00 2001 From: tanya Date: Wed, 13 Jul 2022 19:39:57 +0300 Subject: [PATCH 48/55] Remove unused method --- .../hstest/testing/execution/MainMethodExecutor.java | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/main/java/org/hyperskill/hstest/testing/execution/MainMethodExecutor.java b/src/main/java/org/hyperskill/hstest/testing/execution/MainMethodExecutor.java index 99db73cf..d52acf27 100644 --- a/src/main/java/org/hyperskill/hstest/testing/execution/MainMethodExecutor.java +++ b/src/main/java/org/hyperskill/hstest/testing/execution/MainMethodExecutor.java @@ -224,14 +224,4 @@ public void setUseSeparateClassLoader(boolean value) { } this.useSeparateClassLoader = value; } - - private String getNotFoundClassWithMainMethodMessage(String userPackage) { - String inPackage = ""; - if (!userPackage.isEmpty()) { - inPackage = " in package \"" + userPackage + "\""; - } - return "Cannot find a class with a main method" + inPackage + ".\n" + - "Check if you declared it as \"public static void main(String[] args)\"."; - } - } From 08adf54a71aa8b3bdd0cff39351234466d89bced Mon Sep 17 00:00:00 2001 From: tanya Date: Wed, 27 Jul 2022 14:16:50 +0300 Subject: [PATCH 49/55] Fix search for a package at MainMethodExecutor --- .../hstest/common/ReflectionUtils.java | 17 +++++++++++++++++ .../testing/execution/MainMethodExecutor.java | 2 +- .../package_with_class/Main1.java | 7 +++++++ .../{ => test}/FindClassByPackage.java | 11 +++-------- 4 files changed, 28 insertions(+), 9 deletions(-) create mode 100644 src/test/java/outcomes/separate_package/find_class_by_package/package_with_class/Main1.java rename src/test/java/outcomes/separate_package/find_class_by_package/{ => test}/FindClassByPackage.java (60%) diff --git a/src/main/java/org/hyperskill/hstest/common/ReflectionUtils.java b/src/main/java/org/hyperskill/hstest/common/ReflectionUtils.java index fd7b63f0..10665585 100644 --- a/src/main/java/org/hyperskill/hstest/common/ReflectionUtils.java +++ b/src/main/java/org/hyperskill/hstest/common/ReflectionUtils.java @@ -16,7 +16,9 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.net.URISyntaxException; +import java.net.URL; import java.util.ArrayList; +import java.util.Enumeration; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -267,4 +269,19 @@ public static List> getClassesAnnotatedWith(String annotationPath) { }) .collect(Collectors.toList()); } + + public static boolean isPackage(String packageName) { + try { + final ClassLoader cld = Thread.currentThread().getContextClassLoader(); + if (cld == null) { + throw new UnexpectedError("Can't get class loader."); + } + + String resourcePath = packageName.replace('.', '/'); + final Enumeration resources = cld.getResources(resourcePath); + return resources.hasMoreElements(); + } catch (Exception ex) { + return false; + } + } } diff --git a/src/main/java/org/hyperskill/hstest/testing/execution/MainMethodExecutor.java b/src/main/java/org/hyperskill/hstest/testing/execution/MainMethodExecutor.java index d52acf27..aad9734b 100644 --- a/src/main/java/org/hyperskill/hstest/testing/execution/MainMethodExecutor.java +++ b/src/main/java/org/hyperskill/hstest/testing/execution/MainMethodExecutor.java @@ -67,7 +67,7 @@ private void initByClassInstance(Class clazz) { } private void initByName(String sourceName) { - if (Package.getPackage(sourceName) != null) { + if (ReflectionUtils.isPackage(sourceName)) { initByPackageName(sourceName); } else { initByClassName(sourceName); diff --git a/src/test/java/outcomes/separate_package/find_class_by_package/package_with_class/Main1.java b/src/test/java/outcomes/separate_package/find_class_by_package/package_with_class/Main1.java new file mode 100644 index 00000000..dd8e6af3 --- /dev/null +++ b/src/test/java/outcomes/separate_package/find_class_by_package/package_with_class/Main1.java @@ -0,0 +1,7 @@ +package outcomes.separate_package.find_class_by_package.package_with_class; + +class Main1 { + public static void main(String[] args) { + System.out.print("Class by package"); + } +} diff --git a/src/test/java/outcomes/separate_package/find_class_by_package/FindClassByPackage.java b/src/test/java/outcomes/separate_package/find_class_by_package/test/FindClassByPackage.java similarity index 60% rename from src/test/java/outcomes/separate_package/find_class_by_package/FindClassByPackage.java rename to src/test/java/outcomes/separate_package/find_class_by_package/test/FindClassByPackage.java index 6f537a53..dfc46147 100644 --- a/src/test/java/outcomes/separate_package/find_class_by_package/FindClassByPackage.java +++ b/src/test/java/outcomes/separate_package/find_class_by_package/test/FindClassByPackage.java @@ -1,21 +1,16 @@ -package outcomes.separate_package.find_class_by_package; +package outcomes.separate_package.find_class_by_package.test; import org.hyperskill.hstest.dynamic.input.DynamicTestingMethod; import org.hyperskill.hstest.stage.StageTest; import org.hyperskill.hstest.testcase.CheckResult; import org.hyperskill.hstest.testing.TestedProgram; -class Main1 { - public static void main(String[] args) { - System.out.print("Class by package"); - } -} - public class FindClassByPackage extends StageTest { @DynamicTestingMethod CheckResult test1() { - TestedProgram main = new TestedProgram("outcomes.separate_package.find_class_by_package"); + TestedProgram main = new TestedProgram( + "outcomes.separate_package.find_class_by_package.package_with_class"); return new CheckResult(main.start().equals("Class by package"), ""); } From af2aa09184b16d534809260672cd0f37790f3cab Mon Sep 17 00:00:00 2001 From: meanmail Date: Tue, 27 Sep 2022 17:23:48 +0400 Subject: [PATCH 50/55] GitHub actions --- .github/PULL_REQUEST_TEMPLATE.md | 9 +++ .github/actions/java11/action.yml | 14 ++++ .github/actions/java12/action.yml | 14 ++++ .github/actions/java13/action.yml | 14 ++++ .github/actions/java14/action.yml | 14 ++++ .github/actions/java15/action.yml | 14 ++++ .github/actions/java16/action.yml | 14 ++++ .github/actions/java18/action.yml | 14 ++++ .github/actions/java19/action.yml | 14 ++++ .github/actions/java20/action.yml | 14 ++++ .github/actions/java21/action.yml | 14 ++++ .github/workflows/main.yml | 103 ++++++++++++++++++++++++++++++ 12 files changed, 252 insertions(+) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/actions/java11/action.yml create mode 100644 .github/actions/java12/action.yml create mode 100644 .github/actions/java13/action.yml create mode 100644 .github/actions/java14/action.yml create mode 100644 .github/actions/java15/action.yml create mode 100644 .github/actions/java16/action.yml create mode 100644 .github/actions/java18/action.yml create mode 100644 .github/actions/java19/action.yml create mode 100644 .github/actions/java20/action.yml create mode 100644 .github/actions/java21/action.yml create mode 100644 .github/workflows/main.yml diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..0f9943f1 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,9 @@ +Task: [#HSPC-](https://vyahhi.myjetbrains.com/youtrack/issue/HSPC-) + +**Dependencies** +- ! + +**Reviewers** +- [ ] @ + +**Description** diff --git a/.github/actions/java11/action.yml b/.github/actions/java11/action.yml new file mode 100644 index 00000000..4d9fc4df --- /dev/null +++ b/.github/actions/java11/action.yml @@ -0,0 +1,14 @@ +name: 'Init environment' +description: 'Install dependencies' + +runs: + using: "composite" + steps: + - uses: actions/checkout@v3 + - name: Set up Java 11 + uses: actions/setup-java@v2 + with: + java-version: "11" + - name: Install Dependencies + run: gradle resolveDependencies + shell: bash diff --git a/.github/actions/java12/action.yml b/.github/actions/java12/action.yml new file mode 100644 index 00000000..95d2e3d6 --- /dev/null +++ b/.github/actions/java12/action.yml @@ -0,0 +1,14 @@ +name: 'Init environment' +description: 'Install dependencies' + +runs: + using: "composite" + steps: + - uses: actions/checkout@v3 + - name: Set up Java 12 + uses: actions/setup-java@v2 + with: + java-version: "12" + - name: Install Dependencies + run: gradle resolveDependencies + shell: bash diff --git a/.github/actions/java13/action.yml b/.github/actions/java13/action.yml new file mode 100644 index 00000000..a3352ef2 --- /dev/null +++ b/.github/actions/java13/action.yml @@ -0,0 +1,14 @@ +name: 'Init environment' +description: 'Install dependencies' + +runs: + using: "composite" + steps: + - uses: actions/checkout@v3 + - name: Set up Java 13 + uses: actions/setup-java@v2 + with: + java-version: "13" + - name: Install Dependencies + run: gradle resolveDependencies + shell: bash diff --git a/.github/actions/java14/action.yml b/.github/actions/java14/action.yml new file mode 100644 index 00000000..26b730e0 --- /dev/null +++ b/.github/actions/java14/action.yml @@ -0,0 +1,14 @@ +name: 'Init environment' +description: 'Install dependencies' + +runs: + using: "composite" + steps: + - uses: actions/checkout@v3 + - name: Set up Java 14 + uses: actions/setup-java@v2 + with: + java-version: "14" + - name: Install Dependencies + run: gradle resolveDependencies + shell: bash diff --git a/.github/actions/java15/action.yml b/.github/actions/java15/action.yml new file mode 100644 index 00000000..8a038fa7 --- /dev/null +++ b/.github/actions/java15/action.yml @@ -0,0 +1,14 @@ +name: 'Init environment' +description: 'Install dependencies' + +runs: + using: "composite" + steps: + - uses: actions/checkout@v3 + - name: Set up Java 15 + uses: actions/setup-java@v2 + with: + java-version: "15" + - name: Install Dependencies + run: gradle resolveDependencies + shell: bash diff --git a/.github/actions/java16/action.yml b/.github/actions/java16/action.yml new file mode 100644 index 00000000..01cf3d73 --- /dev/null +++ b/.github/actions/java16/action.yml @@ -0,0 +1,14 @@ +name: 'Init environment' +description: 'Install dependencies' + +runs: + using: "composite" + steps: + - uses: actions/checkout@v3 + - name: Set up Java 16 + uses: actions/setup-java@v2 + with: + java-version: "16" + - name: Install Dependencies + run: gradle resolveDependencies + shell: bash diff --git a/.github/actions/java18/action.yml b/.github/actions/java18/action.yml new file mode 100644 index 00000000..381c3395 --- /dev/null +++ b/.github/actions/java18/action.yml @@ -0,0 +1,14 @@ +name: 'Init environment' +description: 'Install dependencies' + +runs: + using: "composite" + steps: + - uses: actions/checkout@v3 + - name: Set up Java 18 + uses: actions/setup-java@v2 + with: + java-version: "18" + - name: Install Dependencies + run: gradle resolveDependencies + shell: bash diff --git a/.github/actions/java19/action.yml b/.github/actions/java19/action.yml new file mode 100644 index 00000000..667d29bf --- /dev/null +++ b/.github/actions/java19/action.yml @@ -0,0 +1,14 @@ +name: 'Init environment' +description: 'Install dependencies' + +runs: + using: "composite" + steps: + - uses: actions/checkout@v3 + - name: Set up Java 19 + uses: actions/setup-java@v2 + with: + java-version: "19" + - name: Install Dependencies + run: gradle resolveDependencies + shell: bash diff --git a/.github/actions/java20/action.yml b/.github/actions/java20/action.yml new file mode 100644 index 00000000..54c09aa6 --- /dev/null +++ b/.github/actions/java20/action.yml @@ -0,0 +1,14 @@ +name: 'Init environment' +description: 'Install dependencies' + +runs: + using: "composite" + steps: + - uses: actions/checkout@v3 + - name: Set up Java 20 + uses: actions/setup-java@v2 + with: + java-version: "20" + - name: Install Dependencies + run: gradle resolveDependencies + shell: bash diff --git a/.github/actions/java21/action.yml b/.github/actions/java21/action.yml new file mode 100644 index 00000000..d2c9552a --- /dev/null +++ b/.github/actions/java21/action.yml @@ -0,0 +1,14 @@ +name: 'Init environment' +description: 'Install dependencies' + +runs: + using: "composite" + steps: + - uses: actions/checkout@v3 + - name: Set up Java 21 + uses: actions/setup-java@v2 + with: + java-version: "21" + - name: Install Dependencies + run: gradle resolveDependencies + shell: bash diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..0eaba155 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,103 @@ +name: Checks +on: [ push ] + +jobs: + Java_11: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: ./.github/actions/java11 + - name: java version + run: java -version + - name: test + run: gradle test -i + Java_12: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: ./.github/actions/java12 + - name: java version + run: java -version + - name: test + run: gradle test -i + Java_13: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: ./.github/actions/java13 + - name: java version + run: java -version + - name: test + run: gradle test -i + Java_14: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: ./.github/actions/java14 + - name: java version + run: java -version + - name: test + run: gradle test -i + Java_15: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: ./.github/actions/java15 + - name: java version + run: java -version + - name: test + run: gradle test -i + Java_16: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: ./.github/actions/java16 + - name: java version + run: java -version + - name: test + run: gradle test -i + Java_17: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: ./.github/actions/java17 + - name: java version + run: java -version + - name: test + run: gradle test -i + Java_18: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: ./.github/actions/java18 + - name: java version + run: java -version + - name: test + run: gradle test -i + Java_19: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: ./.github/actions/java19 + - name: java version + run: java -version + - name: test + run: gradle test -i + Java_20: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: ./.github/actions/java20 + - name: java version + run: java -version + - name: test + run: gradle test -i + Java_21: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: ./.github/actions/java21 + - name: java version + run: java -version + - name: test + run: gradle test -i From e6136cb33d59d3bda457e4c901dd8d0840be6980 Mon Sep 17 00:00:00 2001 From: meanmail Date: Tue, 27 Sep 2022 17:28:54 +0400 Subject: [PATCH 51/55] Fix GitHub actions --- .github/actions/java11/action.yml | 1 + .github/actions/java12/action.yml | 1 + .github/actions/java13/action.yml | 1 + .github/actions/java14/action.yml | 1 + .github/actions/java15/action.yml | 1 + .github/actions/java16/action.yml | 1 + .github/actions/java18/action.yml | 1 + .github/actions/java19/action.yml | 1 + .github/actions/java20/action.yml | 1 + .github/actions/java21/action.yml | 1 + 10 files changed, 10 insertions(+) diff --git a/.github/actions/java11/action.yml b/.github/actions/java11/action.yml index 4d9fc4df..d3f714b6 100644 --- a/.github/actions/java11/action.yml +++ b/.github/actions/java11/action.yml @@ -8,6 +8,7 @@ runs: - name: Set up Java 11 uses: actions/setup-java@v2 with: + distribution: 'temurin' java-version: "11" - name: Install Dependencies run: gradle resolveDependencies diff --git a/.github/actions/java12/action.yml b/.github/actions/java12/action.yml index 95d2e3d6..69043df2 100644 --- a/.github/actions/java12/action.yml +++ b/.github/actions/java12/action.yml @@ -8,6 +8,7 @@ runs: - name: Set up Java 12 uses: actions/setup-java@v2 with: + distribution: 'temurin' java-version: "12" - name: Install Dependencies run: gradle resolveDependencies diff --git a/.github/actions/java13/action.yml b/.github/actions/java13/action.yml index a3352ef2..ea2477f9 100644 --- a/.github/actions/java13/action.yml +++ b/.github/actions/java13/action.yml @@ -8,6 +8,7 @@ runs: - name: Set up Java 13 uses: actions/setup-java@v2 with: + distribution: 'temurin' java-version: "13" - name: Install Dependencies run: gradle resolveDependencies diff --git a/.github/actions/java14/action.yml b/.github/actions/java14/action.yml index 26b730e0..d5e58279 100644 --- a/.github/actions/java14/action.yml +++ b/.github/actions/java14/action.yml @@ -8,6 +8,7 @@ runs: - name: Set up Java 14 uses: actions/setup-java@v2 with: + distribution: 'temurin' java-version: "14" - name: Install Dependencies run: gradle resolveDependencies diff --git a/.github/actions/java15/action.yml b/.github/actions/java15/action.yml index 8a038fa7..437c966f 100644 --- a/.github/actions/java15/action.yml +++ b/.github/actions/java15/action.yml @@ -8,6 +8,7 @@ runs: - name: Set up Java 15 uses: actions/setup-java@v2 with: + distribution: 'temurin' java-version: "15" - name: Install Dependencies run: gradle resolveDependencies diff --git a/.github/actions/java16/action.yml b/.github/actions/java16/action.yml index 01cf3d73..87bb8403 100644 --- a/.github/actions/java16/action.yml +++ b/.github/actions/java16/action.yml @@ -8,6 +8,7 @@ runs: - name: Set up Java 16 uses: actions/setup-java@v2 with: + distribution: 'temurin' java-version: "16" - name: Install Dependencies run: gradle resolveDependencies diff --git a/.github/actions/java18/action.yml b/.github/actions/java18/action.yml index 381c3395..46d0017a 100644 --- a/.github/actions/java18/action.yml +++ b/.github/actions/java18/action.yml @@ -8,6 +8,7 @@ runs: - name: Set up Java 18 uses: actions/setup-java@v2 with: + distribution: 'temurin' java-version: "18" - name: Install Dependencies run: gradle resolveDependencies diff --git a/.github/actions/java19/action.yml b/.github/actions/java19/action.yml index 667d29bf..d5d29c97 100644 --- a/.github/actions/java19/action.yml +++ b/.github/actions/java19/action.yml @@ -8,6 +8,7 @@ runs: - name: Set up Java 19 uses: actions/setup-java@v2 with: + distribution: 'temurin' java-version: "19" - name: Install Dependencies run: gradle resolveDependencies diff --git a/.github/actions/java20/action.yml b/.github/actions/java20/action.yml index 54c09aa6..e01ec166 100644 --- a/.github/actions/java20/action.yml +++ b/.github/actions/java20/action.yml @@ -8,6 +8,7 @@ runs: - name: Set up Java 20 uses: actions/setup-java@v2 with: + distribution: 'temurin' java-version: "20" - name: Install Dependencies run: gradle resolveDependencies diff --git a/.github/actions/java21/action.yml b/.github/actions/java21/action.yml index d2c9552a..8841b17b 100644 --- a/.github/actions/java21/action.yml +++ b/.github/actions/java21/action.yml @@ -8,6 +8,7 @@ runs: - name: Set up Java 21 uses: actions/setup-java@v2 with: + distribution: 'temurin' java-version: "21" - name: Install Dependencies run: gradle resolveDependencies From 4208fb2b61fc37b20382df96a441353027a5215f Mon Sep 17 00:00:00 2001 From: meanmail Date: Tue, 27 Sep 2022 17:39:19 +0400 Subject: [PATCH 52/55] Fix GitHub actions: distribution zulu --- .github/actions/java11/action.yml | 2 +- .github/actions/java12/action.yml | 2 +- .github/actions/java13/action.yml | 2 +- .github/actions/java14/action.yml | 2 +- .github/actions/java15/action.yml | 2 +- .github/actions/java16/action.yml | 2 +- .github/actions/java17/action.yml | 15 +++++++++++++++ .github/actions/java18/action.yml | 2 +- .github/actions/java19/action.yml | 2 +- .github/actions/java20/action.yml | 2 +- .github/actions/java21/action.yml | 2 +- 11 files changed, 25 insertions(+), 10 deletions(-) create mode 100644 .github/actions/java17/action.yml diff --git a/.github/actions/java11/action.yml b/.github/actions/java11/action.yml index d3f714b6..3d25ee77 100644 --- a/.github/actions/java11/action.yml +++ b/.github/actions/java11/action.yml @@ -8,7 +8,7 @@ runs: - name: Set up Java 11 uses: actions/setup-java@v2 with: - distribution: 'temurin' + distribution: 'zulu' java-version: "11" - name: Install Dependencies run: gradle resolveDependencies diff --git a/.github/actions/java12/action.yml b/.github/actions/java12/action.yml index 69043df2..5c909536 100644 --- a/.github/actions/java12/action.yml +++ b/.github/actions/java12/action.yml @@ -8,7 +8,7 @@ runs: - name: Set up Java 12 uses: actions/setup-java@v2 with: - distribution: 'temurin' + distribution: 'zulu' java-version: "12" - name: Install Dependencies run: gradle resolveDependencies diff --git a/.github/actions/java13/action.yml b/.github/actions/java13/action.yml index ea2477f9..fd77141c 100644 --- a/.github/actions/java13/action.yml +++ b/.github/actions/java13/action.yml @@ -8,7 +8,7 @@ runs: - name: Set up Java 13 uses: actions/setup-java@v2 with: - distribution: 'temurin' + distribution: 'zulu' java-version: "13" - name: Install Dependencies run: gradle resolveDependencies diff --git a/.github/actions/java14/action.yml b/.github/actions/java14/action.yml index d5e58279..de4ac073 100644 --- a/.github/actions/java14/action.yml +++ b/.github/actions/java14/action.yml @@ -8,7 +8,7 @@ runs: - name: Set up Java 14 uses: actions/setup-java@v2 with: - distribution: 'temurin' + distribution: 'zulu' java-version: "14" - name: Install Dependencies run: gradle resolveDependencies diff --git a/.github/actions/java15/action.yml b/.github/actions/java15/action.yml index 437c966f..96741fe6 100644 --- a/.github/actions/java15/action.yml +++ b/.github/actions/java15/action.yml @@ -8,7 +8,7 @@ runs: - name: Set up Java 15 uses: actions/setup-java@v2 with: - distribution: 'temurin' + distribution: 'zulu' java-version: "15" - name: Install Dependencies run: gradle resolveDependencies diff --git a/.github/actions/java16/action.yml b/.github/actions/java16/action.yml index 87bb8403..9c5e976f 100644 --- a/.github/actions/java16/action.yml +++ b/.github/actions/java16/action.yml @@ -8,7 +8,7 @@ runs: - name: Set up Java 16 uses: actions/setup-java@v2 with: - distribution: 'temurin' + distribution: 'zulu' java-version: "16" - name: Install Dependencies run: gradle resolveDependencies diff --git a/.github/actions/java17/action.yml b/.github/actions/java17/action.yml new file mode 100644 index 00000000..69ad23b1 --- /dev/null +++ b/.github/actions/java17/action.yml @@ -0,0 +1,15 @@ +name: 'Init environment' +description: 'Install dependencies' + +runs: + using: "composite" + steps: + - uses: actions/checkout@v3 + - name: Set up Java 17 + uses: actions/setup-java@v2 + with: + distribution: 'zulu' + java-version: "17" + - name: Install Dependencies + run: gradle resolveDependencies + shell: bash diff --git a/.github/actions/java18/action.yml b/.github/actions/java18/action.yml index 46d0017a..f45962cd 100644 --- a/.github/actions/java18/action.yml +++ b/.github/actions/java18/action.yml @@ -8,7 +8,7 @@ runs: - name: Set up Java 18 uses: actions/setup-java@v2 with: - distribution: 'temurin' + distribution: 'zulu' java-version: "18" - name: Install Dependencies run: gradle resolveDependencies diff --git a/.github/actions/java19/action.yml b/.github/actions/java19/action.yml index d5d29c97..7db1a7a5 100644 --- a/.github/actions/java19/action.yml +++ b/.github/actions/java19/action.yml @@ -8,7 +8,7 @@ runs: - name: Set up Java 19 uses: actions/setup-java@v2 with: - distribution: 'temurin' + distribution: 'zulu' java-version: "19" - name: Install Dependencies run: gradle resolveDependencies diff --git a/.github/actions/java20/action.yml b/.github/actions/java20/action.yml index e01ec166..fa134b2a 100644 --- a/.github/actions/java20/action.yml +++ b/.github/actions/java20/action.yml @@ -8,7 +8,7 @@ runs: - name: Set up Java 20 uses: actions/setup-java@v2 with: - distribution: 'temurin' + distribution: 'zulu' java-version: "20" - name: Install Dependencies run: gradle resolveDependencies diff --git a/.github/actions/java21/action.yml b/.github/actions/java21/action.yml index 8841b17b..fd70ad91 100644 --- a/.github/actions/java21/action.yml +++ b/.github/actions/java21/action.yml @@ -8,7 +8,7 @@ runs: - name: Set up Java 21 uses: actions/setup-java@v2 with: - distribution: 'temurin' + distribution: 'zulu' java-version: "21" - name: Install Dependencies run: gradle resolveDependencies From bd690128ffa20b2d337ded9fed6738f8f0290333 Mon Sep 17 00:00:00 2001 From: meanmail Date: Tue, 27 Sep 2022 17:44:13 +0400 Subject: [PATCH 53/55] Update GitHub actions --- .github/actions/java19/action.yml | 15 --------------- .github/actions/java20/action.yml | 15 --------------- .github/actions/java21/action.yml | 15 --------------- 3 files changed, 45 deletions(-) delete mode 100644 .github/actions/java19/action.yml delete mode 100644 .github/actions/java20/action.yml delete mode 100644 .github/actions/java21/action.yml diff --git a/.github/actions/java19/action.yml b/.github/actions/java19/action.yml deleted file mode 100644 index 7db1a7a5..00000000 --- a/.github/actions/java19/action.yml +++ /dev/null @@ -1,15 +0,0 @@ -name: 'Init environment' -description: 'Install dependencies' - -runs: - using: "composite" - steps: - - uses: actions/checkout@v3 - - name: Set up Java 19 - uses: actions/setup-java@v2 - with: - distribution: 'zulu' - java-version: "19" - - name: Install Dependencies - run: gradle resolveDependencies - shell: bash diff --git a/.github/actions/java20/action.yml b/.github/actions/java20/action.yml deleted file mode 100644 index fa134b2a..00000000 --- a/.github/actions/java20/action.yml +++ /dev/null @@ -1,15 +0,0 @@ -name: 'Init environment' -description: 'Install dependencies' - -runs: - using: "composite" - steps: - - uses: actions/checkout@v3 - - name: Set up Java 20 - uses: actions/setup-java@v2 - with: - distribution: 'zulu' - java-version: "20" - - name: Install Dependencies - run: gradle resolveDependencies - shell: bash diff --git a/.github/actions/java21/action.yml b/.github/actions/java21/action.yml deleted file mode 100644 index fd70ad91..00000000 --- a/.github/actions/java21/action.yml +++ /dev/null @@ -1,15 +0,0 @@ -name: 'Init environment' -description: 'Install dependencies' - -runs: - using: "composite" - steps: - - uses: actions/checkout@v3 - - name: Set up Java 21 - uses: actions/setup-java@v2 - with: - distribution: 'zulu' - java-version: "21" - - name: Install Dependencies - run: gradle resolveDependencies - shell: bash From b5651be6cfdb9929d4f7085db96edd4c8d290c17 Mon Sep 17 00:00:00 2001 From: meanmail Date: Tue, 27 Sep 2022 17:45:17 +0400 Subject: [PATCH 54/55] Fix GitHub actions --- .github/workflows/main.yml | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0eaba155..b45bac01 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -74,30 +74,3 @@ jobs: run: java -version - name: test run: gradle test -i - Java_19: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: ./.github/actions/java19 - - name: java version - run: java -version - - name: test - run: gradle test -i - Java_20: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: ./.github/actions/java20 - - name: java version - run: java -version - - name: test - run: gradle test -i - Java_21: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: ./.github/actions/java21 - - name: java version - run: java -version - - name: test - run: gradle test -i From be14c9b5fb104e601f9d7d392edeb391921f82cb Mon Sep 17 00:00:00 2001 From: meanmail Date: Mon, 3 Oct 2022 11:00:48 +0400 Subject: [PATCH 55/55] Bump version v9 -> v10 --- build.gradle | 2 +- .../java/org/hyperskill/hstest/exception/FailureHandler.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index f34165a9..b95bce49 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ plugins { } group 'org.hyperskill' -version '9' +version '10' sourceCompatibility = 11 diff --git a/src/main/java/org/hyperskill/hstest/exception/FailureHandler.java b/src/main/java/org/hyperskill/hstest/exception/FailureHandler.java index c2f6b068..69d0850a 100644 --- a/src/main/java/org/hyperskill/hstest/exception/FailureHandler.java +++ b/src/main/java/org/hyperskill/hstest/exception/FailureHandler.java @@ -33,7 +33,7 @@ public static String getReport() { + "OS " + os + "\n" + "Java " + java + "\n" + "Vendor " + vendor + "\n" - + "Testing library version 9"; + + "Testing library version 10"; } else { return "Submitted via web"; }