diff --git a/build.gradle.kts b/build.gradle.kts index dd19ca4..19a528f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -21,11 +21,12 @@ repositories { } dependencies { - compileOnly("ru.alfabank.tests:akita:4.0.0") // This dependency is used by the application. implementation(libs.guava) implementation("org.springframework.boot:spring-boot-starter-data-mongodb") implementation("org.springframework.boot:spring-boot-starter-web") + implementation("io.cucumber:cucumber-java:7.15.0") + implementation("io.qameta.allure:allure-cucumber7-jvm:2.25.0") compileOnly("org.projectlombok:lombok") annotationProcessor("org.projectlombok:lombok") diff --git a/src/main/java/ru/sakkuratov/autotests/configuration/TestLauncherConfiguration.java b/src/main/java/ru/sakkuratov/autotests/configuration/TestLauncherConfiguration.java index f02b887..07b10a5 100644 --- a/src/main/java/ru/sakkuratov/autotests/configuration/TestLauncherConfiguration.java +++ b/src/main/java/ru/sakkuratov/autotests/configuration/TestLauncherConfiguration.java @@ -1,5 +1,6 @@ package ru.sakkuratov.autotests.configuration; +import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -7,23 +8,30 @@ import org.springframework.context.event.SimpleApplicationEventMulticaster; import org.springframework.core.task.SimpleAsyncTaskExecutor; import org.springframework.core.task.TaskExecutor; +import org.springframework.scheduling.annotation.AsyncConfigurer; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import ru.sakkuratov.autotests.exception.CustomAsyncExceptionHandler; @Configuration -public class TestLauncherConfiguration { - @Value("${") +public class TestLauncherConfiguration implements AsyncConfigurer { + @Value("${spring.async.core-pool-size:1}") + private Integer corePoolSize; + @Value("${spring.async.max-pool-size:3}") + private Integer maxCorePoolSize; + @Value("${ spring.async.queue-capacity:100}") + private Integer queueCapacity; - @Bean("taskExecutor") + @Bean(name = "TaskExecutor") public TaskExecutor taskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); - executor.setCorePoolSize(3); - executor.setMaxPoolSize(3); + executor.setCorePoolSize(corePoolSize); + executor.setMaxPoolSize(maxCorePoolSize); + executor.setQueueCapacity(queueCapacity); executor.setThreadNamePrefix("TaskExecutor::"); executor.setWaitForTasksToCompleteOnShutdown(true); executor.initialize(); return executor; } - @Bean public ApplicationEventMulticaster simpleApplicationEventMulticaster() { SimpleApplicationEventMulticaster eventMulticaster = new SimpleApplicationEventMulticaster(); @@ -32,4 +40,9 @@ public ApplicationEventMulticaster simpleApplicationEventMulticaster() { eventMulticaster.setTaskExecutor(asyncTaskExecutor); return eventMulticaster; } + + @Override + public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { + return new CustomAsyncExceptionHandler(); + } } diff --git a/src/main/java/ru/sakkuratov/autotests/controllers/TaskController.java b/src/main/java/ru/sakkuratov/autotests/controllers/TaskController.java index 7e7a31a..f86a5f7 100644 --- a/src/main/java/ru/sakkuratov/autotests/controllers/TaskController.java +++ b/src/main/java/ru/sakkuratov/autotests/controllers/TaskController.java @@ -1,44 +1,52 @@ package ru.sakkuratov.autotests.controllers; -import lombok.AllArgsConstructor; +import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import ru.sakkuratov.autotests.models.TestParameters; import ru.sakkuratov.autotests.services.StabSystemTaskService; import ru.sakkuratov.autotests.services.StabTestWatcherService; +import ru.sakkuratov.autotests.services.TestWatcher; -import java.util.Map; +import static ru.sakkuratov.autotests.helpers.CommonHelpers.getStackTrace; @RestController public class TaskController { - @Autowired - private StabTestWatcherService testWatcher; - @Autowired - private StabSystemTaskService systemTaskService; - private static final HttpHeaders headers = new HttpHeaders(); static { headers.setContentType(MediaType.APPLICATION_JSON); } + @Autowired + private TestWatcher testWatcher; + @Autowired + private StabTestWatcherService stabTestWatcher; + @Autowired + private StabSystemTaskService systemTaskService; + @PostMapping("task/add") - public ResponseEntity taskAdd(@RequestBody String body) { - Map responseBody = testWatcher.addTask(body); - return ResponseEntity.accepted().headers(headers).body(responseBody); + public ResponseEntity taskAdd(@RequestBody String body) { + try { + TestParameters parameters = new ObjectMapper().readValue(body, TestParameters.class); + return testWatcher.addTask(parameters); + } catch (Exception ex) { + return ResponseEntity.internalServerError().body("Test didn't launch: " + getStackTrace(ex)); + } } @GetMapping("tasks") public ResponseEntity getStatus() { - Object result = testWatcher.getStatus(null); + Object result = stabTestWatcher.getStatus(null); return ResponseEntity.ok().headers(headers).body(result); } @GetMapping("tasks/{id}") public ResponseEntity getStatus(@PathVariable String id) { - Object result = testWatcher.getStatus(id); + Object result = stabTestWatcher.getStatus(id); return ResponseEntity.ok().headers(headers).body(result); } } diff --git a/src/main/java/ru/sakkuratov/autotests/cucumber/steps/CommonSteps.java b/src/main/java/ru/sakkuratov/autotests/cucumber/steps/CommonSteps.java new file mode 100644 index 0000000..b658d6d --- /dev/null +++ b/src/main/java/ru/sakkuratov/autotests/cucumber/steps/CommonSteps.java @@ -0,0 +1,11 @@ +package ru.sakkuratov.autotests.cucumber.steps; + +import io.cucumber.java.bg.И; + +public class CommonSteps { + + @И("^вывести на экран сообщение \"(.+)\"$") + public void showMessage(String message) { + System.out.println(message); + } +} diff --git a/src/main/java/ru/sakkuratov/autotests/events/StartCleanEvent.java b/src/main/java/ru/sakkuratov/autotests/events/StartCleanEvent.java new file mode 100644 index 0000000..f2261a4 --- /dev/null +++ b/src/main/java/ru/sakkuratov/autotests/events/StartCleanEvent.java @@ -0,0 +1,9 @@ +package ru.sakkuratov.autotests.events; + +import org.springframework.context.ApplicationEvent; + +public class StartCleanEvent extends ApplicationEvent { + public StartCleanEvent(Object source) { + super(source); + } +} diff --git a/src/main/java/ru/sakkuratov/autotests/events/TestFinishedEvent.java b/src/main/java/ru/sakkuratov/autotests/events/TestFinishedEvent.java new file mode 100644 index 0000000..4841735 --- /dev/null +++ b/src/main/java/ru/sakkuratov/autotests/events/TestFinishedEvent.java @@ -0,0 +1,22 @@ +package ru.sakkuratov.autotests.events; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationEvent; +import ru.sakkuratov.autotests.models.CucumberTask; + +public class TestFinishedEvent extends ApplicationEvent { + + private static final Logger logger = LoggerFactory.getLogger(TestFinishedEvent.class); + private final String resultMessage; + + public TestFinishedEvent(CucumberTask source, String resultMessage) { + super(source); + this.resultMessage = resultMessage; + logger.debug("Event created: Test finished event"); + } + + public String getResultMessage() { + return this.resultMessage; + } +} diff --git a/src/main/java/ru/sakkuratov/autotests/exception/CustomAsyncExceptionHandler.java b/src/main/java/ru/sakkuratov/autotests/exception/CustomAsyncExceptionHandler.java new file mode 100644 index 0000000..6ae91a3 --- /dev/null +++ b/src/main/java/ru/sakkuratov/autotests/exception/CustomAsyncExceptionHandler.java @@ -0,0 +1,22 @@ +package ru.sakkuratov.autotests.exception; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; + +import java.lang.reflect.Method; + +public class CustomAsyncExceptionHandler implements AsyncUncaughtExceptionHandler { + private static final Logger logger = LoggerFactory.getLogger(CustomAsyncExceptionHandler.class); + + @Override + public void handleUncaughtException(Throwable throwable, Method method, Object... obj) { + + logger.error("Exception message - " + throwable.getMessage()); + logger.error("Method name - " + method.getName()); + for (Object param : obj) { + logger.error("Parameter value - " + param); + } + } + +} diff --git a/src/main/java/ru/sakkuratov/autotests/helpers/CommonHelpers.java b/src/main/java/ru/sakkuratov/autotests/helpers/CommonHelpers.java new file mode 100644 index 0000000..ddc330f --- /dev/null +++ b/src/main/java/ru/sakkuratov/autotests/helpers/CommonHelpers.java @@ -0,0 +1,14 @@ +package ru.sakkuratov.autotests.helpers; + +import java.io.PrintWriter; +import java.io.StringWriter; + +public class CommonHelpers { + + public static String getStackTrace(Throwable exception) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + exception.printStackTrace(pw); + return sw.toString(); + } +} diff --git a/src/main/java/ru/sakkuratov/autotests/models/CucumberTask.java b/src/main/java/ru/sakkuratov/autotests/models/CucumberTask.java new file mode 100644 index 0000000..56c83c0 --- /dev/null +++ b/src/main/java/ru/sakkuratov/autotests/models/CucumberTask.java @@ -0,0 +1,75 @@ +package ru.sakkuratov.autotests.models; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.cucumber.core.cli.Main; +import lombok.Getter; +import lombok.Setter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationEventPublisher; +import ru.sakkuratov.autotests.events.TestFinishedEvent; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import static ru.sakkuratov.autotests.helpers.CommonHelpers.getStackTrace; + +@Getter +@Setter +public class CucumberTask implements Runnable { + + private static final Logger logger = LoggerFactory.getLogger(CucumberTask.class); + private final String id = UUID.randomUUID().toString(); + private final TestParameters parameters = TestParameters.defaultParameters(); + + @JsonIgnore + private final ApplicationEventPublisher publisher; + + public CucumberTask(TestParameters parameters, ApplicationEventPublisher publisher) { + this.publisher = publisher; + this.parameters.setParameters(parameters); + } + + @Override + public void run() { + logger.info("Test has started."); + String resultMessage = "Tests finished successful."; + int exitStatus = Main.run(getCucumberArgs()); + logger.info("Test has finished."); + if (exitStatus != 0) resultMessage = "Test finished with errors."; + publisher.publishEvent(new TestFinishedEvent(this, resultMessage)); + } + + private String[] getCucumberArgs() { + List args = new ArrayList<>(); + args.add("--threads"); + args.add(parameters.getThreads()); + args.add("--glue"); + args.add(parameters.getGlue()); + parameters.getPlugin().forEach(plugin -> { + args.add("--plugin"); + args.add(plugin); + }); + args.add("--tags"); + args.add(parameters.getTags()); + args.add(parameters.getFeaturesPath()); + return args.toArray(new String[0]); + } + + @Override + public String toString() { + try { + return new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(this); + } catch (JsonProcessingException e) { + logger.error("Couldn't parse CucumberTask to JSON. Error: " + getStackTrace(e)); + return id; + } + } + + public Integer getPriority() { + return Integer.valueOf(this.parameters.getPriority()); + } +} diff --git a/src/main/java/ru/sakkuratov/autotests/models/TestParameters.java b/src/main/java/ru/sakkuratov/autotests/models/TestParameters.java new file mode 100644 index 0000000..ae7f5be --- /dev/null +++ b/src/main/java/ru/sakkuratov/autotests/models/TestParameters.java @@ -0,0 +1,72 @@ +package ru.sakkuratov.autotests.models; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.time.Duration; +import java.util.HashSet; +import java.util.Set; + +@Setter +@Getter +@NoArgsConstructor +public class TestParameters { + + private String priority; + private String glue; + private String threads; + private Set plugin = new HashSet<>(); + private String featuresPath; + private String owner; + private String tags; + private String timeout; + + public static TestParameters defaultParameters() { + TestParameters testParameters = new TestParameters(); + testParameters.setPriority("0"); + testParameters.setThreads("1"); + testParameters.setGlue("ru.sakkuratov.autotests.cucumber"); + testParameters.addPlugin("pretty"); + testParameters.addPlugin("io.qameta.allure.cucumber7jvm.AllureCucumber7Jvm"); + testParameters.setFeaturesPath(""); + testParameters.setOwner("DEFAULT"); + testParameters.setTags(""); + testParameters.setTimeout("10S"); + return testParameters; + } + + @JsonIgnore + public long getCucumberRunTimeout() { + return Duration.parse("PT" + timeout).getSeconds(); + } + + public void addPlugin(String plugin) { + this.plugin.add(plugin); + } + + public void setParameters(TestParameters testParameters) { + if (testParameters.getGlue() != null) { + this.glue = testParameters.getGlue(); + } + if (testParameters.getThreads() != null) { + this.threads = testParameters.getThreads(); + } + if (testParameters.getPlugin() != null) { + this.plugin.addAll(testParameters.getPlugin()); + } + if (testParameters.getFeaturesPath() != null) { + this.featuresPath = testParameters.getFeaturesPath(); + } + if (testParameters.getOwner() != null) { + this.owner = testParameters.getOwner(); + } + if (testParameters.getTags() != null) { + this.tags = testParameters.getTags(); + } + if (testParameters.getGlue() != null) { + this.timeout = testParameters.getTimeout(); + } + } +} diff --git a/src/main/java/ru/sakkuratov/autotests/services/TestWatcher.java b/src/main/java/ru/sakkuratov/autotests/services/TestWatcher.java new file mode 100644 index 0000000..e5303af --- /dev/null +++ b/src/main/java/ru/sakkuratov/autotests/services/TestWatcher.java @@ -0,0 +1,34 @@ +package ru.sakkuratov.autotests.services; + +import jakarta.annotation.PostConstruct; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.core.task.TaskExecutor; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import ru.sakkuratov.autotests.models.CucumberTask; +import ru.sakkuratov.autotests.models.TestParameters; + +@Service +public class TestWatcher { + + @Autowired + private TaskExecutor taskExecutor; + @Autowired + private ApplicationEventPublisher publisher; + + private HttpHeaders headers = new HttpHeaders(); + + @PostConstruct + public void init() { + headers.setContentType(MediaType.APPLICATION_JSON); + } + + public ResponseEntity addTask(TestParameters testParameters) { + CucumberTask task = new CucumberTask(testParameters, publisher); + taskExecutor.execute(task); + return ResponseEntity.ok().headers(headers).body(task); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..6eb788c --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,3 @@ +spring.async.core-pool-size=1 +spring.async.max-pool-size=3 +spring.async.queue-capacity=100 \ No newline at end of file diff --git a/src/test/resources/features/test.feature b/src/test/resources/features/test.feature new file mode 100644 index 0000000..265c2af --- /dev/null +++ b/src/test/resources/features/test.feature @@ -0,0 +1,6 @@ +#language: ru +Функция: Default + + @TEST + Сценарий: Тестовый запуск + * вывести на экран сообщение "Hello, World!" \ No newline at end of file