├── images └── architecture.jpg ├── icon └── cucelastic_plugin_icon.jpeg ├── dashboard └── kibana │ ├── Kibana_Indexes_To_Add.PNG │ ├── Kibana_Index_Creation_Page.PNG │ ├── Plugin_listing_on_Cucumber_site.png │ ├── cucumber_elastic_search_kibana_dashboard_1.JPG │ ├── cucumber_elastic_search_kibana_dashboard_2.JPG │ ├── cucumber_elastic_search_kibana_dashboard_3.JPG │ └── cucumber_elastic_search_kibana_dashboard_4.JPG ├── .gitignore ├── src ├── main │ └── java │ │ └── com │ │ └── araj │ │ └── cucumber │ │ └── elasticsearch │ │ ├── json │ │ ├── pojo │ │ │ ├── Row.java │ │ │ ├── Argument.java │ │ │ ├── Match.java │ │ │ ├── DocString.java │ │ │ ├── Tag.java │ │ │ ├── Result.java │ │ │ ├── ResultMatch.java │ │ │ ├── Report.java │ │ │ ├── Step.java │ │ │ └── Element.java │ │ ├── JsonPojoConverter.java │ │ └── postprocessors │ │ │ ├── ElementPostProcessor.java │ │ │ └── ReportPostProcessor.java │ │ ├── pojos │ │ ├── collections │ │ │ ├── ScenarioDetailsCollection.java │ │ │ ├── SummaryCollection.java │ │ │ ├── AllTagsCollection.java │ │ │ ├── AllFeaturesCollection.java │ │ │ └── AllScenariosCollection.java │ │ ├── ReportDetails.java │ │ ├── Feature.java │ │ ├── ResultSender.java │ │ ├── ResultCount.java │ │ ├── ScenarioSummary.java │ │ ├── TagSummary.java │ │ ├── StepSummary.java │ │ └── FeatureSummary.java │ │ ├── exceptions │ │ ├── filesystem │ │ │ ├── FileCreationException.java │ │ │ └── MissingFileException.java │ │ ├── CucElasticPluginException.java │ │ └── properties │ │ │ └── WrongOrMissingPropertyException.java │ │ ├── logging │ │ └── CucElasticPluginLogger.java │ │ ├── constants │ │ └── Status.java │ │ ├── filesystem │ │ ├── FileSystemManager.java │ │ └── FileIO.java │ │ ├── utils │ │ ├── CucElasticPluginUtils.java │ │ └── CucElasticPluginReportGenerator.java │ │ ├── CucElasticPlugin.java │ │ └── properties │ │ └── PropertyManager.java └── test │ └── java │ └── com │ └── araj │ └── cucumber │ └── elasticsearch │ ├── exceptions │ ├── CucElasticPluginExceptionTest.java │ ├── filesystem │ │ ├── FileCreationExceptionTest.java │ │ └── MissingFileExceptionTest.java │ └── properties │ │ └── WrongOrMissingPropertyExceptionTest.java │ ├── constants │ └── StatusTest.java │ ├── json │ ├── pojo │ │ ├── ResultTest.java │ │ ├── ReportTest.java │ │ ├── PojoTest.java │ │ ├── ResultMatchTest.java │ │ └── ElementTest.java │ ├── postprocessors │ │ ├── ElementPostProcessorTest.java │ │ └── ReportPostProcessorTest.java │ └── JsonPojoConverterTest.java │ ├── logging │ └── CluecumberLoggerTest.java │ ├── files │ ├── FileSystemManagerTest.java │ └── FileIOTest.java │ ├── properties │ └── PropertyManagerTest.java │ ├── utils │ └── ReportGeneratorTest.java │ └── CucElasticPluginTest.java ├── USAGE.md ├── README.md ├── pom.xml └── LICENCE.md /images/architecture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AshisRaj/cucelastic-maven-plugin/HEAD/images/architecture.jpg -------------------------------------------------------------------------------- /icon/cucelastic_plugin_icon.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AshisRaj/cucelastic-maven-plugin/HEAD/icon/cucelastic_plugin_icon.jpeg -------------------------------------------------------------------------------- /dashboard/kibana/Kibana_Indexes_To_Add.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AshisRaj/cucelastic-maven-plugin/HEAD/dashboard/kibana/Kibana_Indexes_To_Add.PNG -------------------------------------------------------------------------------- /dashboard/kibana/Kibana_Index_Creation_Page.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AshisRaj/cucelastic-maven-plugin/HEAD/dashboard/kibana/Kibana_Index_Creation_Page.PNG -------------------------------------------------------------------------------- /dashboard/kibana/Plugin_listing_on_Cucumber_site.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AshisRaj/cucelastic-maven-plugin/HEAD/dashboard/kibana/Plugin_listing_on_Cucumber_site.png -------------------------------------------------------------------------------- /dashboard/kibana/cucumber_elastic_search_kibana_dashboard_1.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AshisRaj/cucelastic-maven-plugin/HEAD/dashboard/kibana/cucumber_elastic_search_kibana_dashboard_1.JPG -------------------------------------------------------------------------------- /dashboard/kibana/cucumber_elastic_search_kibana_dashboard_2.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AshisRaj/cucelastic-maven-plugin/HEAD/dashboard/kibana/cucumber_elastic_search_kibana_dashboard_2.JPG -------------------------------------------------------------------------------- /dashboard/kibana/cucumber_elastic_search_kibana_dashboard_3.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AshisRaj/cucelastic-maven-plugin/HEAD/dashboard/kibana/cucumber_elastic_search_kibana_dashboard_3.JPG -------------------------------------------------------------------------------- /dashboard/kibana/cucumber_elastic_search_kibana_dashboard_4.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AshisRaj/cucelastic-maven-plugin/HEAD/dashboard/kibana/cucumber_elastic_search_kibana_dashboard_4.JPG -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | ### Java ### 3 | # Compiled class file 4 | *.class 5 | 6 | # class path 7 | *.classpath 8 | 9 | # Project 10 | *.project 11 | 12 | # settings 13 | .settings 14 | 15 | # Package Files # 16 | *.jar 17 | 18 | ### Maven ### 19 | target/ 20 | -------------------------------------------------------------------------------- /src/main/java/com/araj/cucumber/elasticsearch/json/pojo/Row.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Ashis Raj 3 | */ 4 | 5 | package com.araj.cucumber.elasticsearch.json.pojo; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | public class Row { 11 | private List cells = new ArrayList<>(); 12 | 13 | public List getCells() { 14 | return cells; 15 | } 16 | 17 | public void setCells(final List cells) { 18 | this.cells = cells; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/java/com/araj/cucumber/elasticsearch/exceptions/CucElasticPluginExceptionTest.java: -------------------------------------------------------------------------------- 1 | package com.araj.cucumber.elasticsearch.exceptions; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.assertj.core.api.Assertions.*; 6 | 7 | public class CucElasticPluginExceptionTest { 8 | @Test 9 | public void testErrorMessage() { 10 | CucElasticPluginException exception = new CucElasticPluginException("This is a test"); 11 | assertThat("This is a test").isEqualTo(exception.getMessage()); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/araj/cucumber/elasticsearch/pojos/collections/ScenarioDetailsCollection.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Ashis Raj 3 | */ 4 | 5 | package com.araj.cucumber.elasticsearch.pojos.collections; 6 | 7 | import com.araj.cucumber.elasticsearch.json.pojo.Element; 8 | 9 | public class ScenarioDetailsCollection { 10 | private final Element element; 11 | 12 | public ScenarioDetailsCollection(final Element element) { 13 | this.element = element; 14 | } 15 | 16 | public Element getElement() { 17 | return element; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/araj/cucumber/elasticsearch/json/pojo/Argument.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Ashis Raj 3 | */ 4 | 5 | package com.araj.cucumber.elasticsearch.json.pojo; 6 | 7 | class Argument { 8 | 9 | private String val; 10 | private int offset; 11 | 12 | public String getVal() { 13 | return val; 14 | } 15 | 16 | public void setVal(final String val) { 17 | this.val = val; 18 | } 19 | 20 | public int getOffset() { 21 | return offset; 22 | } 23 | 24 | public void setOffset(final int offset) { 25 | this.offset = offset; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/com/araj/cucumber/elasticsearch/exceptions/filesystem/FileCreationExceptionTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Ashis Raj 3 | */ 4 | 5 | package com.araj.cucumber.elasticsearch.exceptions.filesystem; 6 | 7 | import org.junit.Test; 8 | 9 | import static org.assertj.core.api.Assertions.*; 10 | 11 | public class FileCreationExceptionTest { 12 | 13 | @Test 14 | public void testErrorMessage() { 15 | FileCreationException exception = new FileCreationException("Filename"); 16 | assertThat("File 'Filename' could not be created.").isEqualTo(exception.getMessage()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/test/java/com/araj/cucumber/elasticsearch/exceptions/filesystem/MissingFileExceptionTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Ashis Raj 3 | */ 4 | 5 | package com.araj.cucumber.elasticsearch.exceptions.filesystem; 6 | 7 | import org.junit.Test; 8 | 9 | import static org.assertj.core.api.Assertions.*; 10 | 11 | public class MissingFileExceptionTest { 12 | 13 | @Test 14 | public void testErrorMessage() { 15 | MissingFileException exception = new MissingFileException("Filename"); 16 | assertThat("File 'Filename' could not be found.").isEqualTo(exception.getMessage()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/araj/cucumber/elasticsearch/exceptions/filesystem/FileCreationException.java: -------------------------------------------------------------------------------- 1 | package com.araj.cucumber.elasticsearch.exceptions.filesystem; 2 | 3 | import com.araj.cucumber.elasticsearch.exceptions.CucElasticPluginException; 4 | 5 | /** 6 | * Thrown when a file cannot be created. 7 | */ 8 | public class FileCreationException extends CucElasticPluginException { 9 | /** 10 | * Constructor. 11 | * 12 | * @param fileName The file to be created. 13 | */ 14 | public FileCreationException(final String fileName) { 15 | super("File '" + fileName + "' could not be created."); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/araj/cucumber/elasticsearch/logging/CucElasticPluginLogger.java: -------------------------------------------------------------------------------- 1 | package com.araj.cucumber.elasticsearch.logging; 2 | 3 | import org.apache.maven.plugin.logging.Log; 4 | 5 | import javax.inject.Singleton; 6 | 7 | @Singleton 8 | public class CucElasticPluginLogger { 9 | 10 | private Log mojoLogger; 11 | 12 | public void setMojoLogger(final Log mojoLogger) { 13 | this.mojoLogger = mojoLogger; 14 | } 15 | 16 | public void info(final CharSequence charSequence) { 17 | mojoLogger.info(charSequence); 18 | } 19 | 20 | public void error(final CharSequence charSequence) { 21 | mojoLogger.error(charSequence); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/araj/cucumber/elasticsearch/exceptions/filesystem/MissingFileException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Ashis Raj 3 | */ 4 | 5 | package com.araj.cucumber.elasticsearch.exceptions.filesystem; 6 | 7 | import com.araj.cucumber.elasticsearch.exceptions.CucElasticPluginException; 8 | 9 | /** 10 | * Thrown when a file is not found. 11 | */ 12 | public class MissingFileException extends CucElasticPluginException { 13 | /** 14 | * Constructor. 15 | * 16 | * @param fileName The name of the missing file. 17 | */ 18 | public MissingFileException(final String fileName) { 19 | super("File '" + fileName + "' could not be found."); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/araj/cucumber/elasticsearch/exceptions/CucElasticPluginException.java: -------------------------------------------------------------------------------- 1 | package com.araj.cucumber.elasticsearch.exceptions; 2 | 3 | import org.apache.maven.plugin.MojoExecutionException; 4 | 5 | /** 6 | * The base Cucumber Report plugin exception that all exceptions extend. 7 | * Since this extends the {@link MojoExecutionException}, 8 | * it will stop the plugin execution when thrown. 9 | */ 10 | public class CucElasticPluginException extends MojoExecutionException { 11 | /** 12 | * Constructor. 13 | * 14 | * @param message The error message for the exception. 15 | */ 16 | public CucElasticPluginException(final String message) { 17 | super(message); 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /src/main/java/com/araj/cucumber/elasticsearch/json/pojo/Match.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Ashis Raj 3 | */ 4 | 5 | package com.araj.cucumber.elasticsearch.json.pojo; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | public class Match { 11 | private String location = ""; 12 | private List arguments = new ArrayList<>(); 13 | 14 | public String getLocation() { 15 | return location; 16 | } 17 | 18 | public void setLocation(final String location) { 19 | this.location = location; 20 | } 21 | 22 | public List getArguments() { 23 | return arguments; 24 | } 25 | 26 | public void setArguments(final List arguments) { 27 | this.arguments = arguments; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/test/java/com/araj/cucumber/elasticsearch/exceptions/properties/WrongOrMissingPropertyExceptionTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Ashis Raj 3 | */ 4 | 5 | package com.araj.cucumber.elasticsearch.exceptions.properties; 6 | 7 | import org.junit.Test; 8 | 9 | import static org.assertj.core.api.Assertions.*; 10 | 11 | public class WrongOrMissingPropertyExceptionTest { 12 | 13 | @Test 14 | public void testErrorMessage() { 15 | WrongOrMissingPropertyException exception = 16 | new WrongOrMissingPropertyException("PropertyName"); 17 | assertThat("Property 'PropertyName' is not specified" 18 | + " in the configuration section of your pom file" 19 | + " or contains an invalid value.") 20 | .isEqualTo(exception.getMessage()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/test/java/com/araj/cucumber/elasticsearch/constants/StatusTest.java: -------------------------------------------------------------------------------- 1 | package com.araj.cucumber.elasticsearch.constants; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.hamcrest.core.Is.is; 6 | import static org.junit.Assert.assertThat; 7 | 8 | public class StatusTest { 9 | 10 | @Test 11 | public void getPassedStatusFromStringTest() { 12 | Status skipped = Status.fromString("passed"); 13 | assertThat(skipped, is(Status.PASSED)); 14 | } 15 | 16 | @Test 17 | public void getFailedStatusFromStringTest() { 18 | Status skipped = Status.fromString("failed"); 19 | assertThat(skipped, is(Status.FAILED)); 20 | } 21 | 22 | @Test 23 | public void getSkippedStatusFromStringTest() { 24 | Status skipped = Status.fromString("skipped"); 25 | assertThat(skipped, is(Status.SKIPPED)); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/com/araj/cucumber/elasticsearch/json/pojo/ResultTest.java: -------------------------------------------------------------------------------- 1 | package com.araj.cucumber.elasticsearch.json.pojo; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | 6 | import static org.assertj.core.api.Assertions.*; 7 | 8 | public class ResultTest { 9 | private Result result; 10 | 11 | @Before 12 | public void setup() { 13 | result = new Result(); 14 | } 15 | 16 | @Test 17 | public void hasErrorMessageTest() { 18 | assertThat(result.hasErrorMessage()); 19 | result.setErrorMessage("My Error"); 20 | assertThat(result.hasErrorMessage()).isTrue(); 21 | } 22 | 23 | @Test 24 | public void durationTest() { 25 | result.setDuration(1234567890); 26 | assertThat(1234L).isEqualTo(result.getDurationInMilliseconds()); 27 | assertThat("0m 01s 234ms").isEqualTo(result.returnDurationString()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/araj/cucumber/elasticsearch/json/pojo/DocString.java: -------------------------------------------------------------------------------- 1 | package com.araj.cucumber.elasticsearch.json.pojo; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | public class DocString { 6 | @SerializedName("content_type") 7 | private String contentType = ""; 8 | private int line; 9 | private String value = ""; 10 | 11 | public String getContentType() { 12 | return contentType; 13 | } 14 | 15 | public void setContentType(final String contentType) { 16 | this.contentType = contentType; 17 | } 18 | 19 | public int getLine() { 20 | return line; 21 | } 22 | 23 | public void setLine(final int line) { 24 | this.line = line; 25 | } 26 | 27 | public String getValue() { 28 | return value; 29 | } 30 | 31 | public void setValue(final String value) { 32 | this.value = value; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/araj/cucumber/elasticsearch/exceptions/properties/WrongOrMissingPropertyException.java: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright Ashis Raj 4 | */ 5 | 6 | package com.araj.cucumber.elasticsearch.exceptions.properties; 7 | 8 | import com.araj.cucumber.elasticsearch.exceptions.CucElasticPluginException; 9 | 10 | /** 11 | * Thrown when an expected plugin property is not found or wrong 12 | * (typically set inside a configuration block within the pom file). 13 | */ 14 | public class WrongOrMissingPropertyException extends CucElasticPluginException { 15 | /** 16 | * Constructor. 17 | * 18 | * @param property The name of the missing property. 19 | */ 20 | public WrongOrMissingPropertyException(final String property) { 21 | super("Property '" + property 22 | + "' is not specified in the configuration section " 23 | + "of your pom file or contains an invalid value."); 24 | } 25 | } -------------------------------------------------------------------------------- /src/main/java/com/araj/cucumber/elasticsearch/constants/Status.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Ashis Raj 3 | */ 4 | 5 | package com.araj.cucumber.elasticsearch.constants; 6 | 7 | import java.util.Arrays; 8 | import java.util.List; 9 | 10 | /** 11 | * Enum to manage all states for steps and scenarios. 12 | */ 13 | public enum Status { 14 | PASSED("passed"), 15 | FAILED("failed"), 16 | SKIPPED("skipped"), 17 | PENDING("pending"), 18 | UNDEFINED("undefined"), 19 | AMBIGUOUS("ambiguous"); 20 | 21 | public static final List BASIC_STATES = Arrays.asList(Status.PASSED, Status.FAILED, Status.SKIPPED); 22 | 23 | private final String status; 24 | 25 | Status(final String statusString) { 26 | this.status = statusString; 27 | } 28 | 29 | public static Status fromString(String status) { 30 | return valueOf(status.toUpperCase()); 31 | } 32 | 33 | public String getStatusAsString() { 34 | return status; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/araj/cucumber/elasticsearch/pojos/ReportDetails.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Ashis Raj 3 | */ 4 | 5 | package com.araj.cucumber.elasticsearch.pojos; 6 | 7 | import java.text.DateFormat; 8 | import java.text.SimpleDateFormat; 9 | import java.util.Date; 10 | 11 | public class ReportDetails { 12 | private String chartJson; 13 | private final String date; 14 | private final String pageName; 15 | 16 | public ReportDetails(final String pageName) { 17 | this.pageName = pageName; 18 | DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); 19 | Date date = new Date(); 20 | this.date = dateFormat.format(date); 21 | } 22 | 23 | public String getChartJson() { 24 | return chartJson; 25 | } 26 | 27 | public void setChartJson(final String chartJson) { 28 | this.chartJson = chartJson; 29 | } 30 | 31 | public String getDate() { 32 | return date; 33 | } 34 | 35 | public String getPageName() { 36 | return pageName; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/test/java/com/araj/cucumber/elasticsearch/json/pojo/ReportTest.java: -------------------------------------------------------------------------------- 1 | package com.araj.cucumber.elasticsearch.json.pojo; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | import static org.assertj.core.api.Assertions.*; 10 | 11 | public class ReportTest { 12 | private Report report; 13 | 14 | @Before 15 | public void setup() { 16 | report = new Report(); 17 | } 18 | 19 | @Test 20 | public void getTotalDurationTest() { 21 | List elements = new ArrayList<>(); 22 | Element element = new Element(); 23 | List steps = new ArrayList<>(); 24 | Step step = new Step(); 25 | Result result = new Result(); 26 | result.setDuration(10000000); 27 | step.setResult(result); 28 | steps.add(step); 29 | element.setSteps(steps); 30 | elements.add(element); 31 | report.setElements(elements); 32 | assertThat(10000000L).isEqualTo(report.getTotalDuration()); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/test/java/com/araj/cucumber/elasticsearch/logging/CluecumberLoggerTest.java: -------------------------------------------------------------------------------- 1 | package com.araj.cucumber.elasticsearch.logging; 2 | 3 | import org.apache.maven.plugin.logging.Log; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | 7 | import static org.mockito.Mockito.mock; 8 | import static org.mockito.Mockito.times; 9 | import static org.mockito.Mockito.verify; 10 | 11 | public class CluecumberLoggerTest { 12 | 13 | private Log mockedLogger; 14 | private CucElasticPluginLogger logger; 15 | 16 | @Before 17 | public void setup() { 18 | mockedLogger = mock(Log.class); 19 | logger = new CucElasticPluginLogger(); 20 | logger.setMojoLogger(mockedLogger); 21 | } 22 | 23 | @Test 24 | public void infoTest() { 25 | logger.info("Test"); 26 | verify(mockedLogger, times(1)) 27 | .info("Test"); 28 | } 29 | 30 | @Test 31 | public void errorTest() { 32 | logger.error("Test"); 33 | verify(mockedLogger, times(1)) 34 | .error("Test"); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/test/java/com/araj/cucumber/elasticsearch/files/FileSystemManagerTest.java: -------------------------------------------------------------------------------- 1 | package com.araj.cucumber.elasticsearch.files; 2 | 3 | import com.araj.cucumber.elasticsearch.exceptions.CucElasticPluginException; 4 | import com.araj.cucumber.elasticsearch.filesystem.FileSystemManager; 5 | 6 | import org.junit.Before; 7 | import org.junit.Rule; 8 | import org.junit.Test; 9 | import org.junit.rules.TemporaryFolder; 10 | 11 | public class FileSystemManagerTest { 12 | @Rule 13 | public TemporaryFolder testFolder = new TemporaryFolder(); 14 | private FileSystemManager fileSystemManager; 15 | 16 | @Before 17 | public void setup() { 18 | fileSystemManager = new FileSystemManager(); 19 | } 20 | 21 | @Test(expected = CucElasticPluginException.class) 22 | public void invalidSourceFeaturesTest() throws Exception { 23 | fileSystemManager.getJsonFilePaths("nonexistentpath"); 24 | } 25 | 26 | @Test 27 | public void validSourceFeaturesTest() throws Exception { 28 | String jsonPath = testFolder.getRoot().getPath(); 29 | fileSystemManager.getJsonFilePaths(jsonPath); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/araj/cucumber/elasticsearch/json/pojo/Tag.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Ashis Raj 3 | */ 4 | 5 | package com.araj.cucumber.elasticsearch.json.pojo; 6 | 7 | import java.util.Objects; 8 | 9 | import com.araj.cucumber.elasticsearch.utils.CucElasticPluginUtils; 10 | 11 | public class Tag { 12 | private String name; 13 | 14 | public String getName() { 15 | return name; 16 | } 17 | 18 | public void setName(final String name) { 19 | this.name = name; 20 | } 21 | 22 | public String getUrlFriendlyName() { 23 | return CucElasticPluginUtils.escapeHTML(name).replace("@", ""); 24 | } 25 | 26 | @Override 27 | public String toString() { 28 | return "Tag{" + 29 | "name='" + name + '\'' + 30 | '}'; 31 | } 32 | 33 | @Override 34 | public boolean equals(final Object o) { 35 | if (this == o) return true; 36 | if (o == null || getClass() != o.getClass()) return false; 37 | Tag tag = (Tag) o; 38 | return Objects.equals(name, tag.name); 39 | } 40 | 41 | @Override 42 | public int hashCode() { 43 | return Objects.hash(name); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/araj/cucumber/elasticsearch/pojos/Feature.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Ashis Raj 3 | */ 4 | 5 | package com.araj.cucumber.elasticsearch.pojos; 6 | 7 | import java.util.Objects; 8 | 9 | public class Feature { 10 | private final String name; 11 | private final int index; 12 | 13 | public Feature(final String name, final int index) { 14 | this.name = name; 15 | this.index = index; 16 | } 17 | 18 | public String getName() { 19 | return name; 20 | } 21 | 22 | public int getIndex() { 23 | return index; 24 | } 25 | 26 | @Override 27 | public String toString() { 28 | return "Feature{" + 29 | "name='" + name + '\'' + 30 | ", index='" + index + '\'' + 31 | '}'; 32 | } 33 | 34 | @Override 35 | public boolean equals(final Object o) { 36 | if (this == o) return true; 37 | if (o == null || getClass() != o.getClass()) return false; 38 | Feature feature = (Feature) o; 39 | return index == feature.index && 40 | Objects.equals(name, feature.name); 41 | } 42 | 43 | @Override 44 | public int hashCode() { 45 | return Objects.hash(name, index); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/araj/cucumber/elasticsearch/pojos/ResultSender.java: -------------------------------------------------------------------------------- 1 | package com.araj.cucumber.elasticsearch.pojos; 2 | 3 | import java.util.List; 4 | 5 | import javax.inject.Inject; 6 | import javax.inject.Singleton; 7 | 8 | import com.araj.cucumber.elasticsearch.logging.CucElasticPluginLogger; 9 | import com.fasterxml.jackson.databind.ObjectMapper; 10 | import com.mashape.unirest.http.Unirest; 11 | 12 | @Singleton 13 | public class ResultSender { 14 | 15 | private static final ObjectMapper OM = new ObjectMapper(); 16 | private static final String CONTENT_TYPE = "Content-Type"; 17 | private static final String CONTENT_TYPE_VALUE = "application/json"; 18 | private final CucElasticPluginLogger logger; 19 | 20 | @Inject 21 | public ResultSender(final CucElasticPluginLogger logger) { 22 | this.logger = logger; 23 | } 24 | 25 | public void sendTOElasticSearch(List summaries, String url) { 26 | for(Object summary : summaries) { 27 | try { 28 | logger.info(OM.writeValueAsString(summary)); 29 | Unirest.post(url).header(CONTENT_TYPE, CONTENT_TYPE_VALUE) 30 | .body(OM.writeValueAsString(summary)).asJson(); 31 | } catch (Exception e) { 32 | logger.info(e.toString()); 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/araj/cucumber/elasticsearch/pojos/ResultCount.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Ashis Raj 3 | */ 4 | 5 | package com.araj.cucumber.elasticsearch.pojos; 6 | 7 | public class ResultCount { 8 | private int passed; 9 | private int failed; 10 | private int skipped; 11 | 12 | public ResultCount() { 13 | this.passed = 0; 14 | this.failed = 0; 15 | this.skipped = 0; 16 | } 17 | 18 | public void addPassed(int passedCount) { 19 | this.passed += passedCount; 20 | } 21 | 22 | public void addFailed(int failedCount) { 23 | this.failed += failedCount; 24 | } 25 | 26 | public void addSkipped(int skippedCount) { 27 | this.skipped += skippedCount; 28 | } 29 | 30 | public int getPassed() { 31 | return passed; 32 | } 33 | 34 | public int getFailed() { 35 | return failed; 36 | } 37 | 38 | public int getSkipped() { 39 | return skipped; 40 | } 41 | 42 | public int getTotal() { 43 | return passed + failed + skipped; 44 | } 45 | 46 | @Override 47 | public String toString() { 48 | return "ResultCount{" + 49 | "passed=" + passed + 50 | ", failed=" + failed + 51 | ", skipped=" + skipped + 52 | '}'; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/araj/cucumber/elasticsearch/pojos/ScenarioSummary.java: -------------------------------------------------------------------------------- 1 | package com.araj.cucumber.elasticsearch.pojos; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | public class ScenarioSummary { 6 | 7 | @JsonProperty("feature_index") 8 | private int featureIndex; 9 | 10 | @JsonProperty("featureName") 11 | private String featureName; 12 | 13 | @JsonProperty("scenario_index") 14 | private int scenarioIndex; 15 | 16 | @JsonProperty("scenarioName") 17 | private String scenarioName; 18 | 19 | @JsonProperty("status") 20 | private String status; 21 | 22 | @JsonProperty("date") 23 | private String date; 24 | 25 | public void setFeatureIndex(int featureIndex) { 26 | this.featureIndex = featureIndex; 27 | } 28 | public void setFeatureName(String featureName) { 29 | this.featureName = featureName; 30 | } 31 | 32 | public void setScenarioIndex(int scenarioIndex) { 33 | this.scenarioIndex = scenarioIndex; 34 | } 35 | 36 | public void setscenarioName(String scenarioName) { 37 | this.scenarioName = scenarioName; 38 | } 39 | 40 | public void setStatus(String status) { 41 | this.status = status; 42 | } 43 | 44 | public void setDate(String date) { 45 | this.date = date; 46 | } 47 | } 48 | 49 | -------------------------------------------------------------------------------- /src/main/java/com/araj/cucumber/elasticsearch/pojos/TagSummary.java: -------------------------------------------------------------------------------- 1 | package com.araj.cucumber.elasticsearch.pojos; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | public class TagSummary { 6 | 7 | @JsonProperty("tag_name") 8 | private String tagName; 9 | 10 | @JsonProperty("total_scenarios") 11 | private int totalScenarios; 12 | 13 | @JsonProperty("passed_scenarios") 14 | private int passedScenarios; 15 | 16 | @JsonProperty("failed_scenarios") 17 | private int failedScenarios; 18 | 19 | @JsonProperty("skipped_scenarios") 20 | private int skippedScenarios; 21 | 22 | @JsonProperty("date") 23 | private String date; 24 | 25 | public void setTagName(String tagName) { 26 | this.tagName = tagName; 27 | } 28 | 29 | public void setTotalScenarios(int totalScenarios) { 30 | this.totalScenarios = totalScenarios; 31 | } 32 | 33 | public void setPassedScenarios(int passedScenarios) { 34 | this.passedScenarios = passedScenarios; 35 | } 36 | 37 | public void setFailedScenarios(int failedScenarios) { 38 | this.failedScenarios = failedScenarios; 39 | } 40 | 41 | public void setSkippedScenarios(int skippedScenarios) { 42 | this.skippedScenarios = skippedScenarios; 43 | } 44 | 45 | public void setDate(String date) { 46 | this.date = date; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/araj/cucumber/elasticsearch/pojos/StepSummary.java: -------------------------------------------------------------------------------- 1 | package com.araj.cucumber.elasticsearch.pojos; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | public class StepSummary { 6 | 7 | @JsonProperty("scenario_index") 8 | private int scenarioIndex; 9 | 10 | @JsonProperty("scenarioName") 11 | private String scenarioName; 12 | 13 | @JsonProperty("total_steps") 14 | private int totalSteps; 15 | 16 | @JsonProperty("passed_steps") 17 | private int passedSteps; 18 | 19 | @JsonProperty("failed_steps") 20 | private int failedSteps; 21 | 22 | @JsonProperty("skipped_steps") 23 | private int skippedSteps; 24 | 25 | @JsonProperty("date") 26 | private String date; 27 | 28 | public void setScenarioIndex(int scenarioIndex) { 29 | this.scenarioIndex = scenarioIndex; 30 | } 31 | 32 | public void setscenarioName(String scenarioName) { 33 | this.scenarioName = scenarioName; 34 | } 35 | 36 | public void setTotalSteps(int totalSteps) { 37 | this.totalSteps = totalSteps; 38 | } 39 | 40 | public void setPassedSteps(int passedSteps) { 41 | this.passedSteps = passedSteps; 42 | } 43 | 44 | public void setFailedSteps(int failedSteps) { 45 | this.failedSteps = failedSteps; 46 | } 47 | 48 | public void setSkippedSteps(int skippedSteps) { 49 | this.skippedSteps = skippedSteps; 50 | } 51 | 52 | public void setDate(String date) { 53 | this.date = date; 54 | } 55 | } 56 | 57 | -------------------------------------------------------------------------------- /src/main/java/com/araj/cucumber/elasticsearch/json/pojo/Result.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Ashis Raj 3 | */ 4 | 5 | package com.araj.cucumber.elasticsearch.json.pojo; 6 | 7 | import com.araj.cucumber.elasticsearch.constants.Status; 8 | import com.araj.cucumber.elasticsearch.utils.CucElasticPluginUtils; 9 | import com.google.gson.annotations.SerializedName; 10 | 11 | public class Result { 12 | 13 | private long duration = 0; 14 | private String status = Status.UNDEFINED.toString(); 15 | 16 | @SerializedName("error_message") 17 | private String errorMessage = ""; 18 | 19 | public long getDuration() { 20 | return duration; 21 | } 22 | 23 | public void setDuration(final long duration) { 24 | this.duration = duration; 25 | } 26 | 27 | public String getStatus() { 28 | return status; 29 | } 30 | 31 | public void setStatus(final String status) { 32 | this.status = status; 33 | } 34 | 35 | public boolean hasErrorMessage() { 36 | return errorMessage != null && !errorMessage.trim().isEmpty(); 37 | } 38 | 39 | public String getErrorMessage() { 40 | return errorMessage; 41 | } 42 | 43 | public void setErrorMessage(final String errorMessage) { 44 | this.errorMessage = errorMessage; 45 | } 46 | 47 | public long getDurationInMilliseconds() { 48 | return CucElasticPluginUtils.convertMicrosecondsToMilliseconds(duration); 49 | } 50 | 51 | public String returnDurationString() { 52 | return CucElasticPluginUtils.convertMicrosecondsToTimeString(duration); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/araj/cucumber/elasticsearch/json/JsonPojoConverter.java: -------------------------------------------------------------------------------- 1 | package com.araj.cucumber.elasticsearch.json; 2 | 3 | import javax.inject.Inject; 4 | import javax.inject.Singleton; 5 | 6 | import com.araj.cucumber.elasticsearch.exceptions.CucElasticPluginException; 7 | import com.araj.cucumber.elasticsearch.json.pojo.Element; 8 | import com.araj.cucumber.elasticsearch.json.pojo.Report; 9 | import com.araj.cucumber.elasticsearch.json.postprocessors.ElementPostProcessor; 10 | import com.araj.cucumber.elasticsearch.json.postprocessors.ReportPostProcessor; 11 | import com.google.gson.Gson; 12 | import com.google.gson.JsonParseException; 13 | 14 | import io.gsonfire.GsonFireBuilder; 15 | 16 | @Singleton 17 | public class JsonPojoConverter { 18 | 19 | private final Gson gsonParserWithProcessors; 20 | 21 | @Inject 22 | public JsonPojoConverter(final ReportPostProcessor reportPostProcessor, final ElementPostProcessor elementPostProcessor) { 23 | GsonFireBuilder builder = new GsonFireBuilder() 24 | .registerPostProcessor(Report.class, reportPostProcessor) 25 | .registerPostProcessor(Element.class, elementPostProcessor); 26 | gsonParserWithProcessors = builder.createGson(); 27 | } 28 | 29 | public Report[] convertJsonToReportPojos(final String json) throws CucElasticPluginException { 30 | Report[] reports; 31 | try { 32 | reports = gsonParserWithProcessors.fromJson(json, Report[].class); 33 | } catch (JsonParseException e) { 34 | throw new CucElasticPluginException(e.getMessage()); 35 | } 36 | return reports; 37 | } 38 | } 39 | 40 | -------------------------------------------------------------------------------- /src/main/java/com/araj/cucumber/elasticsearch/pojos/collections/SummaryCollection.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Ashis Raj 3 | */ 4 | 5 | package com.araj.cucumber.elasticsearch.pojos.collections; 6 | 7 | import java.util.Collection; 8 | 9 | import com.araj.cucumber.elasticsearch.constants.Status; 10 | import com.araj.cucumber.elasticsearch.pojos.ResultCount; 11 | 12 | class SummaryCollection { 13 | 14 | void updateResultCount(ResultCount resultCount, final Status status) { 15 | switch (status) { 16 | case PASSED: 17 | resultCount.addPassed(1); 18 | break; 19 | case FAILED: 20 | resultCount.addFailed(1); 21 | break; 22 | case SKIPPED: 23 | case PENDING: 24 | case UNDEFINED: 25 | case AMBIGUOUS: 26 | resultCount.addSkipped(1); 27 | break; 28 | } 29 | } 30 | 31 | int getNumberOfResultsWithStatus(final Collection resultCounts, final Status status) { 32 | int sum = 0; 33 | for (ResultCount resultCount : resultCounts) { 34 | switch (status) { 35 | case PASSED: 36 | sum += resultCount.getPassed(); 37 | break; 38 | case FAILED: 39 | sum += resultCount.getFailed(); 40 | break; 41 | case SKIPPED: 42 | case PENDING: 43 | case UNDEFINED: 44 | case AMBIGUOUS: 45 | sum += resultCount.getSkipped(); 46 | break; 47 | } 48 | } 49 | return sum; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/test/java/com/araj/cucumber/elasticsearch/json/pojo/PojoTest.java: -------------------------------------------------------------------------------- 1 | package com.araj.cucumber.elasticsearch.json.pojo; 2 | 3 | import com.openpojo.reflection.PojoClass; 4 | import com.openpojo.reflection.filters.FilterPackageInfo; 5 | import com.openpojo.reflection.impl.PojoClassFactory; 6 | import com.openpojo.validation.Validator; 7 | import com.openpojo.validation.ValidatorBuilder; 8 | import com.openpojo.validation.affirm.Affirm; 9 | import com.openpojo.validation.test.impl.GetterTester; 10 | import com.openpojo.validation.test.impl.SetterTester; 11 | import org.junit.BeforeClass; 12 | import org.junit.Test; 13 | 14 | import java.util.List; 15 | 16 | public class PojoTest { 17 | private static final int EXPECTED_CLASS_COUNT = 11; 18 | private static final String POJO_PACKAGE = "com.araj.cucumber.elasticsearch.json.pojo"; 19 | 20 | @BeforeClass 21 | public static void setSystemProperty() { 22 | System.setProperty("sun.boot.class.path", System.getProperty("java.class.path")); 23 | } 24 | 25 | @Test 26 | public void ensureExpectedPojoCount() { 27 | List pojoClasses = PojoClassFactory.getPojoClasses(POJO_PACKAGE, 28 | pojoClass -> !pojoClass.getSourcePath().contains("/test-classes/")); 29 | Affirm.affirmEquals("Classes added / removed?", EXPECTED_CLASS_COUNT, pojoClasses.size()); 30 | } 31 | 32 | @Test 33 | public void testPojoStructureAndBehavior() { 34 | Validator validator = ValidatorBuilder.create() 35 | .with(new SetterTester()) 36 | .with(new GetterTester()) 37 | .build(); 38 | 39 | validator.validate(POJO_PACKAGE, new FilterPackageInfo()); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/araj/cucumber/elasticsearch/pojos/FeatureSummary.java: -------------------------------------------------------------------------------- 1 | package com.araj.cucumber.elasticsearch.pojos; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | public class FeatureSummary { 6 | 7 | @JsonProperty("feature_index") 8 | private int featureIndex; 9 | 10 | @JsonProperty("feature_name") 11 | private String featureName; 12 | 13 | @JsonProperty("total_scenarios") 14 | private int totalScenarios; 15 | 16 | @JsonProperty("passed_scenarios") 17 | private int passedScenarios; 18 | 19 | @JsonProperty("failed_scenarios") 20 | private int failedScenarios; 21 | 22 | @JsonProperty("skipped_scenarios") 23 | private int skippedScenarios; 24 | 25 | @JsonProperty("status") 26 | private String status; 27 | 28 | @JsonProperty("date") 29 | private String date; 30 | 31 | public void setfeatureIndex(int featureIndex) { 32 | this.featureIndex = featureIndex; 33 | } 34 | 35 | public void setfeatureName(String featureName) { 36 | this.featureName = featureName; 37 | } 38 | 39 | public void setTotalScenarios(int totalScenarios) { 40 | this.totalScenarios = totalScenarios; 41 | } 42 | 43 | public void setPassedScenarios(int passedScenarios) { 44 | this.passedScenarios = passedScenarios; 45 | } 46 | 47 | public void setFailedScenarios(int failedScenarios) { 48 | this.failedScenarios = failedScenarios; 49 | } 50 | 51 | public void setSkippedScenarios(int skippedScenarios) { 52 | this.skippedScenarios = skippedScenarios; 53 | } 54 | 55 | public void setStatus(String status) { 56 | this.status = status; 57 | } 58 | 59 | public void setDate(String date) { 60 | this.date = date; 61 | } 62 | } 63 | 64 | -------------------------------------------------------------------------------- /src/test/java/com/araj/cucumber/elasticsearch/properties/PropertyManagerTest.java: -------------------------------------------------------------------------------- 1 | package com.araj.cucumber.elasticsearch.properties; 2 | 3 | import static org.hamcrest.core.Is.is; 4 | import static org.junit.Assert.assertThat; 5 | import static org.mockito.ArgumentMatchers.anyString; 6 | import static org.mockito.Mockito.mock; 7 | import static org.mockito.Mockito.times; 8 | import static org.mockito.Mockito.verify; 9 | 10 | import org.junit.Before; 11 | import org.junit.Test; 12 | 13 | import com.araj.cucumber.elasticsearch.exceptions.properties.WrongOrMissingPropertyException; 14 | import com.araj.cucumber.elasticsearch.logging.CucElasticPluginLogger; 15 | 16 | public class PropertyManagerTest { 17 | private PropertyManager propertyManager; 18 | private CucElasticPluginLogger logger; 19 | 20 | @Before 21 | public void setup() { 22 | logger = mock(CucElasticPluginLogger.class); 23 | propertyManager = new PropertyManager(logger); 24 | } 25 | 26 | @Test 27 | public void sourceJsonReportDirectoryTest() { 28 | propertyManager.setSourceJsonReportDirectory("test"); 29 | assertThat(propertyManager.getSourceJsonReportDirectory(), is("test")); 30 | } 31 | 32 | @Test(expected = WrongOrMissingPropertyException.class) 33 | public void missingJsonReportDirectoryTest() throws Exception { 34 | propertyManager.validateSettings(); 35 | } 36 | 37 | @Test(expected = WrongOrMissingPropertyException.class) 38 | public void missingHtmlReportDirectoryTest() throws Exception { 39 | propertyManager.setSourceJsonReportDirectory("test"); 40 | propertyManager.validateSettings(); 41 | } 42 | 43 | @Test 44 | public void logFullPropertiesTest() { 45 | propertyManager.logProperties(); 46 | verify(logger, times(15)).info(anyString()); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/test/java/com/araj/cucumber/elasticsearch/files/FileIOTest.java: -------------------------------------------------------------------------------- 1 | package com.araj.cucumber.elasticsearch.files; 2 | 3 | 4 | import org.junit.Rule; 5 | import org.junit.Test; 6 | import org.junit.rules.TemporaryFolder; 7 | 8 | import com.araj.cucumber.elasticsearch.filesystem.FileIO; 9 | 10 | import com.araj.cucumber.elasticsearch.exceptions.filesystem.FileCreationException; 11 | import com.araj.cucumber.elasticsearch.exceptions.filesystem.MissingFileException; 12 | 13 | import static org.assertj.core.api.Assertions.*; 14 | 15 | public class FileIOTest { 16 | @Rule 17 | public TemporaryFolder testFolder = new TemporaryFolder(); 18 | private FileIO fileIO = new FileIO(); 19 | 20 | @Test(expected = FileCreationException.class) 21 | public void writeToInvalidFileTest() throws Exception { 22 | fileIO.writeContentToFile((byte[]) null, ""); 23 | } 24 | 25 | @Test 26 | public void fileReadWriteTest() throws Exception { 27 | String testString = "This is a test!"; 28 | String path = testFolder.getRoot().getPath().concat("/test.temp"); 29 | fileIO.writeContentToFile(testString, path); 30 | assertThat(testString).isEqualTo(fileIO.readContentFromFile(path)); 31 | } 32 | 33 | @Test(expected = FileCreationException.class) 34 | public void writeContentToFileStringInvalidTest() throws Exception { 35 | String testString = "This is a test!"; 36 | String path = testFolder.getRoot().getPath().substring(1); 37 | fileIO.writeContentToFile(testString, path); 38 | } 39 | 40 | @Test(expected = FileCreationException.class) 41 | public void writeContentToFileByteArrayInvalidTest() throws Exception { 42 | String path = testFolder.getRoot().getPath(); 43 | fileIO.writeContentToFile(new byte[]{}, path); 44 | } 45 | 46 | @Test(expected = MissingFileException.class) 47 | public void readFromMissingFileTest() throws Exception { 48 | String wrongPath = testFolder.getRoot().getPath().concat("/missing.temp"); 49 | fileIO.readContentFromFile(wrongPath); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/araj/cucumber/elasticsearch/json/postprocessors/ElementPostProcessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Ashis Raj 3 | */ 4 | 5 | package com.araj.cucumber.elasticsearch.json.postprocessors; 6 | 7 | import javax.inject.Inject; 8 | import javax.inject.Singleton; 9 | 10 | import com.araj.cucumber.elasticsearch.filesystem.FileIO; 11 | import com.araj.cucumber.elasticsearch.json.pojo.Element; 12 | import com.araj.cucumber.elasticsearch.logging.CucElasticPluginLogger; 13 | import com.araj.cucumber.elasticsearch.properties.PropertyManager; 14 | import com.google.gson.Gson; 15 | import com.google.gson.JsonElement; 16 | 17 | import io.gsonfire.PostProcessor; 18 | 19 | @Singleton 20 | public class ElementPostProcessor implements PostProcessor { 21 | 22 | private final PropertyManager propertyManager; 23 | private final FileIO fileIO; 24 | private final CucElasticPluginLogger logger; 25 | 26 | private int scenarioIndex = 1; 27 | 28 | @Inject 29 | public ElementPostProcessor( 30 | final PropertyManager propertyManager, 31 | final FileIO fileIO, 32 | final CucElasticPluginLogger logger 33 | ) { 34 | this.propertyManager = propertyManager; 35 | this.fileIO = fileIO; 36 | this.logger = logger; 37 | } 38 | 39 | @Override 40 | public void postDeserialize(final Element element, final JsonElement jsonElement, final Gson gson) { 41 | addScenarioIndex(element); 42 | } 43 | 44 | /** 45 | * Add index to scenarios (used for link creation to the detail reports). 46 | * 47 | * @param element The current {@link Element}. 48 | */ 49 | private void addScenarioIndex(final Element element) { 50 | // Filter out background scenarios 51 | if (!element.isScenario()) { 52 | return; 53 | } 54 | element.setScenarioIndex(scenarioIndex); 55 | scenarioIndex++; 56 | } 57 | 58 | @Override 59 | public void postSerialize(final JsonElement jsonElement, final Element element, final Gson gson) { 60 | // not used 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/araj/cucumber/elasticsearch/filesystem/FileSystemManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Ashis Raj 3 | */ 4 | 5 | package com.araj.cucumber.elasticsearch.filesystem; 6 | 7 | import java.io.IOException; 8 | import java.nio.file.Files; 9 | import java.nio.file.Path; 10 | import java.nio.file.Paths; 11 | import java.util.List; 12 | import java.util.stream.Collectors; 13 | 14 | import javax.inject.Singleton; 15 | 16 | import com.araj.cucumber.elasticsearch.exceptions.CucElasticPluginException; 17 | 18 | @Singleton 19 | public class FileSystemManager { 20 | /** 21 | * Return a list of JSON files in a given directory. 22 | * 23 | * @param sourcePath The path in which to search for JSON files. 24 | * @return A list of JSON file paths. 25 | * @throws CucElasticPluginException see {@link CucElasticPluginException}. 26 | */ 27 | public List getJsonFilePaths(final String sourcePath) throws CucElasticPluginException { 28 | List jsonFilePaths; 29 | try { 30 | jsonFilePaths = 31 | Files.walk(Paths.get(sourcePath)) 32 | .filter(Files::isRegularFile) 33 | .filter(p -> p.toString().toLowerCase().endsWith(".json")) 34 | .collect(Collectors.toList()); 35 | 36 | } catch (IOException e) { 37 | throw new CucElasticPluginException( 38 | "Unable to traverse JSON files in " + sourcePath); 39 | } 40 | return jsonFilePaths; 41 | } 42 | 43 | /** 44 | * Copy file to a new location. 45 | * 46 | * @param source The source file. 47 | * @param destination The destination file. 48 | * @throws CucElasticPluginException see {@link CucElasticPluginException}. 49 | */ 50 | public void copyResource(final String source, final String destination) throws CucElasticPluginException { 51 | Path sourcePath = Paths.get(source); 52 | Path destinationPath = Paths.get(destination); 53 | try { 54 | Files.copy(sourcePath, destinationPath); 55 | } catch (IOException e) { 56 | throw new CucElasticPluginException("Cannot copy resource '" + source + "': " + e.getMessage()); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/araj/cucumber/elasticsearch/json/postprocessors/ReportPostProcessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Ashis Raj 3 | */ 4 | 5 | package com.araj.cucumber.elasticsearch.json.postprocessors; 6 | 7 | import com.araj.cucumber.elasticsearch.json.pojo.Element; 8 | import com.araj.cucumber.elasticsearch.json.pojo.Report; 9 | import com.google.gson.Gson; 10 | import com.google.gson.JsonElement; 11 | 12 | import io.gsonfire.PostProcessor; 13 | 14 | import javax.inject.Inject; 15 | import javax.inject.Singleton; 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | 19 | @Singleton 20 | public class ReportPostProcessor implements PostProcessor { 21 | 22 | private final List featureUris; 23 | 24 | @Inject 25 | public ReportPostProcessor() { 26 | featureUris = new ArrayList<>(); 27 | } 28 | 29 | @Override 30 | public void postDeserialize(final Report report, final JsonElement jsonElement, final Gson gson) { 31 | mergeBackgroundScenarios(report); 32 | addFeatureIndex(report); 33 | } 34 | 35 | private void addFeatureIndex(final Report report) { 36 | if (report == null) return; 37 | 38 | String featureName = report.getName(); 39 | if (!featureUris.contains(featureName)) { 40 | featureUris.add(featureName); 41 | } 42 | report.setFeatureIndex(featureUris.indexOf(featureName)); 43 | } 44 | 45 | private void mergeBackgroundScenarios(final Report report) { 46 | List cleanedUpElements = new ArrayList<>(); 47 | Element currentBackgroundElement = null; 48 | 49 | for (Element element : report.getElements()) { 50 | if (element.getType().equalsIgnoreCase("background")) { 51 | currentBackgroundElement = element; 52 | } else { 53 | if (currentBackgroundElement != null) { 54 | element.getSteps().addAll(0, currentBackgroundElement.getSteps()); 55 | } 56 | cleanedUpElements.add(element); 57 | } 58 | } 59 | report.setElements(cleanedUpElements); 60 | } 61 | 62 | @Override 63 | public void postSerialize(final JsonElement jsonElement, final Report report, final Gson gson) { 64 | // not used 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/araj/cucumber/elasticsearch/json/pojo/ResultMatch.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Ashis Raj 3 | */ 4 | 5 | package com.araj.cucumber.elasticsearch.json.pojo; 6 | 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | import com.araj.cucumber.elasticsearch.constants.Status; 12 | 13 | public class ResultMatch { 14 | private Result result; 15 | private Match match; 16 | 17 | private List output = new ArrayList<>(); 18 | 19 | public Result getResult() { 20 | return result != null ? result : new Result(); 21 | } 22 | 23 | public void setResult(final Result result) { 24 | this.result = result; 25 | } 26 | 27 | public Match getMatch() { 28 | return match != null ? match : new Match(); 29 | } 30 | 31 | public void setMatch(final Match match) { 32 | this.match = match; 33 | } 34 | 35 | public List getOutput() { 36 | return output; 37 | } 38 | 39 | public void setOutput(final List output) { 40 | this.output = output; 41 | } 42 | 43 | public String getGlueMethodName() { 44 | return getMatch().getLocation(); 45 | } 46 | 47 | public List getArguments() { 48 | return getMatch().getArguments(); 49 | } 50 | 51 | public Status getStatus() { 52 | return Status.fromString(getResult().getStatus()); 53 | } 54 | 55 | public String getStatusString() { 56 | return getStatus().getStatusAsString(); 57 | } 58 | 59 | public boolean isFailed() { 60 | return getStatus() == Status.FAILED; 61 | } 62 | 63 | public boolean isPassed() { 64 | return getStatus() == Status.PASSED; 65 | } 66 | 67 | public boolean isSkipped() { 68 | return getConsolidatedStatus() == Status.SKIPPED; 69 | } 70 | 71 | public Status getConsolidatedStatus() { 72 | switch (getStatus()) { 73 | case SKIPPED: 74 | case PENDING: 75 | case UNDEFINED: 76 | case AMBIGUOUS: 77 | return Status.SKIPPED; 78 | default: 79 | return getStatus(); 80 | } 81 | } 82 | 83 | public String getConsolidatedStatusString() { 84 | return getConsolidatedStatus().getStatusAsString(); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/com/araj/cucumber/elasticsearch/json/pojo/Report.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Ashis Raj 3 | */ 4 | 5 | package com.araj.cucumber.elasticsearch.json.pojo; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | public class Report { 11 | private int line; 12 | private List elements = new ArrayList<>(); 13 | private String name = ""; 14 | private String description = ""; 15 | private String id = ""; 16 | private String keyword = ""; 17 | private String uri = ""; 18 | 19 | private transient int featureIndex = -1; 20 | 21 | public int getLine() { 22 | return line; 23 | } 24 | 25 | public void setLine(final int line) { 26 | this.line = line; 27 | } 28 | 29 | public List getElements() { 30 | return elements; 31 | } 32 | 33 | public void setElements(final List elements) { 34 | this.elements = elements; 35 | } 36 | 37 | public String getName() { 38 | return !name.isEmpty() ? name : "[Unnamed]"; 39 | } 40 | 41 | public void setName(final String name) { 42 | this.name = name; 43 | } 44 | 45 | public String getDescription() { 46 | return description; 47 | } 48 | 49 | public void setDescription(final String description) { 50 | this.description = description; 51 | } 52 | 53 | public String getId() { 54 | return id; 55 | } 56 | 57 | public void setId(final String id) { 58 | this.id = id; 59 | } 60 | 61 | public String getKeyword() { 62 | return keyword; 63 | } 64 | 65 | public void setKeyword(final String keyword) { 66 | this.keyword = keyword; 67 | } 68 | 69 | public String getUri() { 70 | return uri; 71 | } 72 | 73 | public void setUri(final String uri) { 74 | this.uri = uri; 75 | } 76 | 77 | public int getFeatureIndex() { 78 | return featureIndex; 79 | } 80 | 81 | public void setFeatureIndex(final int featureIndex) { 82 | this.featureIndex = featureIndex; 83 | } 84 | 85 | public long getTotalDuration() { 86 | long totalDurationMicroseconds = 0; 87 | for (Element element : elements) { 88 | totalDurationMicroseconds += element.getTotalDuration(); 89 | } 90 | return totalDurationMicroseconds; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/test/java/com/araj/cucumber/elasticsearch/json/postprocessors/ElementPostProcessorTest.java: -------------------------------------------------------------------------------- 1 | package com.araj.cucumber.elasticsearch.json.postprocessors; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | import static org.mockito.Mockito.mock; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | import javax.activation.MimeType; 10 | 11 | import org.junit.Before; 12 | import org.junit.Test; 13 | 14 | import com.araj.cucumber.elasticsearch.filesystem.FileIO; 15 | import com.araj.cucumber.elasticsearch.json.pojo.Element; 16 | import com.araj.cucumber.elasticsearch.json.pojo.Step; 17 | import com.araj.cucumber.elasticsearch.logging.CucElasticPluginLogger; 18 | import com.araj.cucumber.elasticsearch.properties.PropertyManager; 19 | 20 | public class ElementPostProcessorTest { 21 | private ElementPostProcessor elementPostProcessor; 22 | 23 | @Before 24 | public void setup() { 25 | PropertyManager propertyManager = mock(PropertyManager.class); 26 | FileIO fileIO = mock(FileIO.class); 27 | CucElasticPluginLogger logger = new CucElasticPluginLogger(); 28 | elementPostProcessor = new ElementPostProcessor(propertyManager, fileIO, logger); 29 | } 30 | 31 | @Test 32 | public void postDesiralizeAddScenarioIndexBackgroundScenarioTest() { 33 | Element element = new Element(); 34 | assertThat(0).isEqualTo(element.getScenarioIndex()); 35 | elementPostProcessor.postDeserialize(element, null, null); 36 | assertThat(0).isEqualTo(element.getScenarioIndex()); 37 | } 38 | 39 | @Test 40 | public void postDesiralizeAddScenarioIndexTest() { 41 | Element element = new Element(); 42 | element.setType("scenario"); 43 | assertThat(0).isEqualTo(element.getScenarioIndex()); 44 | elementPostProcessor.postDeserialize(element, null, null); 45 | assertThat(1).isEqualTo(element.getScenarioIndex()); 46 | } 47 | 48 | @Test 49 | public void postDeserializeTest() { 50 | Element element = new Element(); 51 | List steps = new ArrayList<>(); 52 | Step step = new Step(); 53 | steps.add(step); 54 | element.setSteps(steps); 55 | 56 | elementPostProcessor.postDeserialize(element, null, null); 57 | } 58 | 59 | @Test 60 | public void postSerializeTest() { 61 | elementPostProcessor.postSerialize(null, null, null); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/test/java/com/araj/cucumber/elasticsearch/utils/ReportGeneratorTest.java: -------------------------------------------------------------------------------- 1 | package com.araj.cucumber.elasticsearch.utils; 2 | 3 | import static org.mockito.ArgumentMatchers.anyString; 4 | import static org.mockito.Mockito.mock; 5 | import static org.mockito.Mockito.times; 6 | import static org.mockito.Mockito.verify; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | import org.junit.Before; 12 | import org.junit.Test; 13 | 14 | import com.araj.cucumber.elasticsearch.json.pojo.Element; 15 | import com.araj.cucumber.elasticsearch.json.pojo.Report; 16 | import com.araj.cucumber.elasticsearch.logging.CucElasticPluginLogger; 17 | import com.araj.cucumber.elasticsearch.pojos.ResultSender; 18 | import com.araj.cucumber.elasticsearch.pojos.collections.AllScenariosCollection; 19 | import com.araj.cucumber.elasticsearch.properties.PropertyManager; 20 | 21 | public class ReportGeneratorTest { 22 | 23 | private CucElasticPluginLogger cucElasticPluginLogger; 24 | private ResultSender resultSender; 25 | 26 | private CucElasticPluginReportGenerator reportGenerator; 27 | 28 | @Before 29 | public void setup() { 30 | cucElasticPluginLogger = mock(CucElasticPluginLogger.class); 31 | 32 | CucElasticPluginLogger logger = mock(CucElasticPluginLogger.class); 33 | PropertyManager propertyManager = new PropertyManager(logger); 34 | resultSender = mock(ResultSender.class); 35 | reportGenerator = new CucElasticPluginReportGenerator(cucElasticPluginLogger, propertyManager, resultSender); 36 | } 37 | 38 | @Test 39 | public void loggingOperationsTest() throws Exception { 40 | AllScenariosCollection allScenariosPageCollection = new AllScenariosCollection(); 41 | 42 | Report report1 = new Report(); 43 | List elements1 = new ArrayList<>(); 44 | Element element1 = new Element(); 45 | elements1.add(element1); 46 | report1.setElements(elements1); 47 | 48 | Report report2 = new Report(); 49 | List elements2 = new ArrayList<>(); 50 | Element element2 = new Element(); 51 | elements2.add(element2); 52 | report2.setElements(elements2); 53 | 54 | Report[] reportList = {report1, report2}; 55 | allScenariosPageCollection.addReports(reportList); 56 | 57 | reportGenerator.generateAndSendReportDocumentsForElasticSearch(allScenariosPageCollection); 58 | 59 | verify(cucElasticPluginLogger, times(4)).info(anyString()); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/araj/cucumber/elasticsearch/json/pojo/Step.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Ashis Raj 3 | */ 4 | 5 | package com.araj.cucumber.elasticsearch.json.pojo; 6 | 7 | import com.google.gson.annotations.SerializedName; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | import java.util.regex.Matcher; 12 | import java.util.regex.Pattern; 13 | 14 | public class Step extends ResultMatch { 15 | private List before = new ArrayList<>(); 16 | private int line; 17 | private String name = ""; 18 | private String keyword = ""; 19 | private List rows = new ArrayList<>(); 20 | private List after = new ArrayList<>(); 21 | @SerializedName("doc_string") 22 | private DocString docString; 23 | 24 | public List getBefore() { 25 | return before; 26 | } 27 | 28 | public void setBefore(final List before) { 29 | this.before = before; 30 | } 31 | 32 | public int getLine() { 33 | return line; 34 | } 35 | 36 | public void setLine(final int line) { 37 | this.line = line; 38 | } 39 | 40 | public String getName() { 41 | return !name.isEmpty() ? name : "[Unnamed]"; 42 | } 43 | 44 | public void setName(final String name) { 45 | this.name = name; 46 | } 47 | 48 | public String returnNameWithArguments() { 49 | String tmpName = name; 50 | List arguments = getArguments(); 51 | for (int i = arguments.size() - 1; i >= 0; i--) { 52 | String argument = arguments.get(i).getVal(); 53 | if (argument != null) { 54 | tmpName = tmpName.replaceFirst(Pattern.quote(argument), Matcher.quoteReplacement("" + argument + "")); 55 | } 56 | } 57 | return tmpName; 58 | } 59 | 60 | public String getKeyword() { 61 | return keyword; 62 | } 63 | 64 | public void setKeyword(final String keyword) { 65 | this.keyword = keyword; 66 | } 67 | 68 | 69 | public List getRows() { 70 | return rows; 71 | } 72 | 73 | public void setRows(final List rows) { 74 | this.rows = rows; 75 | } 76 | 77 | public List getAfter() { 78 | return after; 79 | } 80 | 81 | public void setAfter(final List after) { 82 | this.after = after; 83 | } 84 | 85 | public DocString getDocString() { 86 | return docString; 87 | } 88 | 89 | public void setDocString(final DocString docString) { 90 | this.docString = docString; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/com/araj/cucumber/elasticsearch/pojos/collections/AllTagsCollection.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Ashis Raj 3 | */ 4 | 5 | package com.araj.cucumber.elasticsearch.pojos.collections; 6 | 7 | 8 | import java.util.HashMap; 9 | import java.util.List; 10 | import java.util.Map; 11 | import java.util.Set; 12 | 13 | import com.araj.cucumber.elasticsearch.constants.Status; 14 | import com.araj.cucumber.elasticsearch.json.pojo.Element; 15 | import com.araj.cucumber.elasticsearch.json.pojo.Report; 16 | import com.araj.cucumber.elasticsearch.json.pojo.Tag; 17 | import com.araj.cucumber.elasticsearch.pojos.ResultCount; 18 | 19 | public class AllTagsCollection extends SummaryCollection { 20 | private Map tagResultCounts; 21 | 22 | public AllTagsCollection(List reports) { 23 | calculateTagResultCounts(reports); 24 | } 25 | 26 | /** 27 | * Get a map of {@link ResultCount} lists connected to tag names. 28 | * 29 | * @return a map of {@link ResultCount} lists with tags as keys. 30 | */ 31 | public Map getTagResultCounts() { 32 | return tagResultCounts; 33 | } 34 | 35 | public Set getTags() { 36 | return tagResultCounts.keySet(); 37 | } 38 | 39 | public int getTotalNumberOfTags() { 40 | return tagResultCounts.size(); 41 | } 42 | 43 | public int getTotalNumberOfPassedTags() { 44 | return getNumberOfResultsWithStatus(tagResultCounts.values(), Status.PASSED); 45 | } 46 | 47 | public int getTotalNumberOfFailedTags() { 48 | return getNumberOfResultsWithStatus(tagResultCounts.values(), Status.FAILED); 49 | } 50 | 51 | public int getTotalNumberOfSkippedTags() { 52 | return getNumberOfResultsWithStatus(tagResultCounts.values(), Status.SKIPPED); 53 | } 54 | 55 | /** 56 | * Calculate the numbers of failures, successes and skips per tag name. 57 | * 58 | * @param reports The {@link Report} list. 59 | */ 60 | private void calculateTagResultCounts(final List reports) { 61 | if (reports == null) return; 62 | tagResultCounts = new HashMap<>(); 63 | for (Report report : reports) { 64 | for (Element element : report.getElements()) { 65 | for (Tag tag : element.getTags()) { 66 | ResultCount tagResultCount = tagResultCounts.getOrDefault(tag, new ResultCount()); 67 | updateResultCount(tagResultCount, element.getStatus()); 68 | tagResultCounts.put(tag, tagResultCount); 69 | } 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/com/araj/cucumber/elasticsearch/filesystem/FileIO.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Ashis Raj 3 | */ 4 | 5 | package com.araj.cucumber.elasticsearch.filesystem; 6 | 7 | import javax.inject.Singleton; 8 | 9 | import com.araj.cucumber.elasticsearch.exceptions.filesystem.FileCreationException; 10 | import com.araj.cucumber.elasticsearch.exceptions.filesystem.MissingFileException; 11 | 12 | import java.io.IOException; 13 | import java.io.PrintStream; 14 | import java.nio.file.Files; 15 | import java.nio.file.Path; 16 | import java.nio.file.Paths; 17 | 18 | import static java.nio.file.Files.readAllBytes; 19 | 20 | /** 21 | * This class manages reading from and writing to files. 22 | */ 23 | @Singleton 24 | public class FileIO { 25 | /** 26 | * Write string content to a file. 27 | * 28 | * @param content the string content to be written. 29 | * @param filePath the complete path to the target file. 30 | * @throws FileCreationException a {@link FileCreationException} in case the file cannot be created. 31 | */ 32 | public void writeContentToFile(final String content, final String filePath) throws FileCreationException { 33 | try (PrintStream ps = new PrintStream(filePath)) { 34 | ps.println(content); 35 | } catch (IOException e) { 36 | throw new FileCreationException(filePath); 37 | } 38 | } 39 | 40 | /** 41 | * Write byte array content to a file. 42 | * 43 | * @param content the byte array content to be written. 44 | * @param filePath the complete path to the target file. 45 | * @throws FileCreationException a {@link FileCreationException} in case the file cannot be created. 46 | */ 47 | public void writeContentToFile(final byte[] content, final String filePath) throws FileCreationException { 48 | Path path = Paths.get(filePath); 49 | try { 50 | Files.write(path, content); 51 | } catch (Exception e) { 52 | throw new FileCreationException(path.toString()); 53 | } 54 | } 55 | 56 | /** 57 | * Read string content from a file. 58 | * 59 | * @param filePath the complete path to the source file. 60 | * @return the file contents as a string. 61 | * @throws MissingFileException a {@link MissingFileException} in case the file does not exist. 62 | */ 63 | public String readContentFromFile(final String filePath) throws MissingFileException { 64 | try { 65 | byte[] bytes = readAllBytes(Paths.get(filePath)); 66 | return new String(bytes).trim(); 67 | } catch (IOException e) { 68 | throw new MissingFileException(filePath); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/araj/cucumber/elasticsearch/utils/CucElasticPluginUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Ashis Raj 3 | */ 4 | 5 | package com.araj.cucumber.elasticsearch.utils; 6 | 7 | import java.time.Duration; 8 | 9 | public class CucElasticPluginUtils { 10 | private static final int MICROSECOND_FACTOR = 1000000; 11 | 12 | /** 13 | * Convert microseconds to a human readable time string. 14 | * 15 | * @param microseconds The amount of microseconds. 16 | * @return The human readable string representation. 17 | */ 18 | public static String convertMicrosecondsToTimeString(final long microseconds) { 19 | Duration durationMilliseconds = Duration.ofMillis(microseconds / MICROSECOND_FACTOR); 20 | long minutes = durationMilliseconds.toMinutes(); 21 | long seconds = durationMilliseconds.minusMinutes(minutes).getSeconds(); 22 | long milliseconds = durationMilliseconds.minusMinutes(minutes).minusSeconds(seconds).toMillis(); 23 | return String.format("%dm %02ds %03dms", minutes, seconds, milliseconds); 24 | } 25 | 26 | /** 27 | * Convert microseconds to milliseconds. 28 | * 29 | * @param microseconds The amount of microseconds. 30 | * @return The millisecond representation. 31 | */ 32 | public static long convertMicrosecondsToMilliseconds(final long microseconds) { 33 | return Duration.ofMillis(microseconds / MICROSECOND_FACTOR).toMillis(); 34 | } 35 | 36 | /** 37 | * Return the current Cucumber version. 38 | * 39 | * @return The version string. 40 | */ 41 | public static String getPluginVersion() { 42 | String version = CucElasticPluginUtils.class.getPackage().getImplementationVersion(); 43 | if (version == null) { 44 | version = "unknown"; 45 | } 46 | return version; 47 | } 48 | 49 | /** 50 | * Escape HTML tags in a string. 51 | * 52 | * @param sourceString The source string. 53 | * @return The escaped string. 54 | */ 55 | public static String escapeHTML(final String sourceString) { 56 | StringBuilder stringBuilder = new StringBuilder(Math.max(16, sourceString.length())); 57 | for (int i = 0; i < sourceString.length(); i++) { 58 | char character = sourceString.charAt(i); 59 | if (character > 127 || character == '"' || character == '<' || character == '>' || character == '&') { 60 | stringBuilder.append("&#"); 61 | stringBuilder.append((int) character); 62 | stringBuilder.append(';'); 63 | } else { 64 | stringBuilder.append(character); 65 | } 66 | } 67 | return stringBuilder.toString(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/test/java/com/araj/cucumber/elasticsearch/CucElasticPluginTest.java: -------------------------------------------------------------------------------- 1 | package com.araj.cucumber.elasticsearch; 2 | 3 | 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | 7 | import com.araj.cucumber.elasticsearch.CucElasticPlugin; 8 | import com.araj.cucumber.elasticsearch.exceptions.CucElasticPluginException; 9 | import com.araj.cucumber.elasticsearch.filesystem.FileIO; 10 | import com.araj.cucumber.elasticsearch.filesystem.FileSystemManager; 11 | import com.araj.cucumber.elasticsearch.json.JsonPojoConverter; 12 | import com.araj.cucumber.elasticsearch.logging.CucElasticPluginLogger; 13 | import com.araj.cucumber.elasticsearch.properties.PropertyManager; 14 | import com.araj.cucumber.elasticsearch.utils.CucElasticPluginReportGenerator; 15 | 16 | import java.nio.file.Path; 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | 20 | import static org.mockito.ArgumentMatchers.any; 21 | import static org.mockito.ArgumentMatchers.anyString; 22 | import static org.mockito.Mockito.mock; 23 | import static org.mockito.Mockito.when; 24 | 25 | public class CucElasticPluginTest { 26 | 27 | private CucElasticPlugin cucElasticPlugin; 28 | private JsonPojoConverter jsonPojoConverter; 29 | private FileIO fileIO; 30 | 31 | @Before 32 | public void setup() throws CucElasticPluginException { 33 | CucElasticPluginLogger cluecumberLogger = mock(CucElasticPluginLogger.class); 34 | PropertyManager propertyManager = mock(PropertyManager.class); 35 | 36 | FileSystemManager fileSystemManager = mock(FileSystemManager.class); 37 | List fileList = new ArrayList<>(); 38 | Path path = mock(Path.class); 39 | fileList.add(path); 40 | when(fileSystemManager.getJsonFilePaths(anyString())).thenReturn(fileList); 41 | 42 | fileIO = mock(FileIO.class); 43 | jsonPojoConverter = mock(JsonPojoConverter.class); 44 | CucElasticPluginReportGenerator reportGenerator = mock(CucElasticPluginReportGenerator.class); 45 | cucElasticPlugin = new CucElasticPlugin( 46 | cluecumberLogger, 47 | propertyManager, 48 | fileSystemManager, 49 | fileIO, 50 | jsonPojoConverter, 51 | reportGenerator 52 | ); 53 | } 54 | 55 | @Test 56 | public void executeTest() throws CucElasticPluginException { 57 | cucElasticPlugin.execute(); 58 | } 59 | 60 | @Test 61 | public void noErrorOnUnparsableJsonTest() throws CucElasticPluginException { 62 | when(fileIO.readContentFromFile(any())).thenReturn("json"); 63 | when(jsonPojoConverter.convertJsonToReportPojos("json")).thenThrow(new CucElasticPluginException("failure")); 64 | cucElasticPlugin.execute(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/test/java/com/araj/cucumber/elasticsearch/json/postprocessors/ReportPostProcessorTest.java: -------------------------------------------------------------------------------- 1 | package com.araj.cucumber.elasticsearch.json.postprocessors; 2 | 3 | import com.araj.cucumber.elasticsearch.json.pojo.Element; 4 | import com.araj.cucumber.elasticsearch.json.pojo.Report; 5 | import com.araj.cucumber.elasticsearch.json.pojo.Step; 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | import static org.assertj.core.api.Assertions.*; 13 | 14 | public class ReportPostProcessorTest { 15 | private ReportPostProcessor reportPostProcessor; 16 | 17 | @Before 18 | public void setup() { 19 | reportPostProcessor = new ReportPostProcessor(); 20 | } 21 | 22 | @Test 23 | public void postDeserializeTest() { 24 | Report report = new Report(); 25 | 26 | List elements = new ArrayList<>(); 27 | 28 | Element backgroundElement = new Element(); 29 | List backgroundSteps = new ArrayList<>(); 30 | 31 | Step backgroundStep1 = new Step(); 32 | backgroundStep1.setName("background step 1"); 33 | backgroundSteps.add(backgroundStep1); 34 | 35 | Step backgroundStep2 = new Step(); 36 | backgroundStep2.setName("background step 2"); 37 | backgroundSteps.add(backgroundStep2); 38 | 39 | backgroundElement.setSteps(backgroundSteps); 40 | backgroundElement.setType("background"); 41 | elements.add(backgroundElement); 42 | 43 | Element element = new Element(); 44 | List steps = new ArrayList<>(); 45 | Step step = new Step(); 46 | step.setName("element step 1"); 47 | steps.add(step); 48 | Step step2 = new Step(); 49 | step2.setName("element step 2"); 50 | steps.add(step2); 51 | element.setSteps(steps); 52 | elements.add(element); 53 | 54 | report.setElements(elements); 55 | 56 | assertThat(2).isEqualTo(report.getElements().size()); 57 | reportPostProcessor.postDeserialize(report, null, null); 58 | assertThat(1).isEqualTo(report.getElements().size()); 59 | List firstElementSteps = report.getElements().get(0).getSteps(); 60 | assertThat(4).isEqualTo(firstElementSteps.size()); 61 | assertThat("background step 1").isEqualTo(firstElementSteps.get(0).getName()); 62 | assertThat("background step 2").isEqualTo(firstElementSteps.get(1).getName()); 63 | assertThat("element step 1").isEqualTo(firstElementSteps.get(2).getName()); 64 | assertThat("element step 2").isEqualTo(firstElementSteps.get(3).getName()); 65 | } 66 | 67 | @Test 68 | public void postSerializeTest() { 69 | reportPostProcessor.postSerialize(null, null, null); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /USAGE.md: -------------------------------------------------------------------------------- 1 | ## Prerequisites 2 | 3 | ### Default Indexes to be created manually in Kibana before using the Plugin 4 | Note: The plugin will pick these indexes as defaults, hence no need to define them in the pom file. 5 | 6 | 7 | 8 | ## Sample of plugin entry to go into your Cucumber project's pom file. 9 | ``` 10 | 11 | com.araj.cucumber.elasticsearch 12 | cucelastic-maven-plugin 13 | 1.0 14 | 15 | 16 | cucumber-elastic-search 17 | verify 18 | 19 | load 20 | 21 | 22 | 23 | 24 | 27 | false 28 | 37 | ${project.build.directory}\cucumber-reports\json-reports 38 | 39 | 44 | 45 | 46 | true 47 | true 48 | true 49 | true 50 | 51 | 55 | localhost:9200 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | ``` 69 | -------------------------------------------------------------------------------- /src/main/java/com/araj/cucumber/elasticsearch/pojos/collections/AllFeaturesCollection.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Ashis Raj 3 | */ 4 | 5 | package com.araj.cucumber.elasticsearch.pojos.collections; 6 | 7 | import java.util.HashMap; 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.Set; 11 | 12 | import com.araj.cucumber.elasticsearch.constants.Status; 13 | import com.araj.cucumber.elasticsearch.json.pojo.Element; 14 | import com.araj.cucumber.elasticsearch.json.pojo.Report; 15 | import com.araj.cucumber.elasticsearch.pojos.Feature; 16 | import com.araj.cucumber.elasticsearch.pojos.ResultCount; 17 | 18 | public class AllFeaturesCollection extends SummaryCollection { 19 | private Map resultCounts; 20 | 21 | public AllFeaturesCollection(final List reports) { 22 | calculateFeatureResultCounts(reports); 23 | } 24 | 25 | /** 26 | * Get a map of {@link ResultCount} lists connected to features. 27 | * 28 | * @return a map of {@link ResultCount} lists with features as keys. 29 | */ 30 | public Map getFeatureResultCounts() { 31 | return resultCounts; 32 | } 33 | 34 | public Set getFeatures() { 35 | return resultCounts.keySet(); 36 | } 37 | 38 | public int getTotalNumberOfFeatures() { 39 | return resultCounts.size(); 40 | } 41 | 42 | public int getTotalNumberOfPassedFeatures() { 43 | return getNumberOfResultsWithStatus(resultCounts.values(), Status.PASSED); 44 | } 45 | 46 | public int getTotalNumberOfFailedFeatures() { 47 | return getNumberOfResultsWithStatus(resultCounts.values(), Status.FAILED); 48 | } 49 | 50 | public int getTotalNumberOfSkippedFeatures() { 51 | return getNumberOfResultsWithStatus(resultCounts.values(), Status.SKIPPED); 52 | } 53 | 54 | public boolean hasFailedScenarios() { 55 | return getTotalNumberOfFailedFeatures() > 0; 56 | } 57 | 58 | public boolean hasPassedScenarios() { 59 | return getTotalNumberOfPassedFeatures() > 0; 60 | } 61 | 62 | public boolean hasSkippedScenarios() { 63 | return getTotalNumberOfSkippedFeatures() > 0; 64 | } 65 | /** 66 | * Calculate the numbers of failures, successes and skips per feature. 67 | * 68 | * @param reports The {@link Report} list. 69 | */ 70 | private void calculateFeatureResultCounts(final List reports) { 71 | if (reports == null) return; 72 | resultCounts = new HashMap<>(); 73 | for (Report report : reports) { 74 | Feature feature = new Feature(report.getName(), report.getFeatureIndex()); 75 | ResultCount featureResultCount = this.resultCounts.getOrDefault(feature, new ResultCount()); 76 | for (Element element : report.getElements()) { 77 | updateResultCount(featureResultCount, element.getStatus()); 78 | } 79 | this.resultCounts.put(feature, featureResultCount); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/com/araj/cucumber/elasticsearch/pojos/collections/AllScenariosCollection.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Ashis Raj 3 | */ 4 | 5 | package com.araj.cucumber.elasticsearch.pojos.collections; 6 | 7 | import java.util.ArrayList; 8 | import java.util.Arrays; 9 | import java.util.List; 10 | 11 | import com.araj.cucumber.elasticsearch.constants.Status; 12 | import com.araj.cucumber.elasticsearch.json.pojo.Element; 13 | import com.araj.cucumber.elasticsearch.json.pojo.Report; 14 | import com.araj.cucumber.elasticsearch.json.pojo.Tag; 15 | import com.araj.cucumber.elasticsearch.pojos.Feature; 16 | import com.araj.cucumber.elasticsearch.utils.CucElasticPluginUtils; 17 | 18 | public class AllScenariosCollection { 19 | private List reports = new ArrayList<>(); 20 | private Tag tagFilter; 21 | private Feature featureFilter; 22 | 23 | public List getReports() { 24 | return reports; 25 | } 26 | 27 | public void clearReports() { 28 | reports = new ArrayList<>(); 29 | } 30 | 31 | public void addReports(final Report[] reportList) { 32 | if (reportList == null) { 33 | return; 34 | } 35 | this.reports.addAll(Arrays.asList(reportList)); 36 | } 37 | 38 | public int getTotalNumberOfScenarios() { 39 | return reports.stream().map(Report::getElements). 40 | mapToInt(elements -> (int) elements.stream().filter(Element::isScenario).count()).sum(); 41 | } 42 | 43 | public boolean hasFailedScenarios() { 44 | return getTotalNumberOfFailedScenarios() > 0; 45 | } 46 | 47 | public boolean hasPassedScenarios() { 48 | return getTotalNumberOfPassedScenarios() > 0; 49 | } 50 | 51 | public boolean hasSkippedScenarios() { 52 | return getTotalNumberOfSkippedScenarios() > 0; 53 | } 54 | 55 | public int getTotalNumberOfPassedScenarios() { 56 | return getNumberOfScenariosWithStatus(Status.PASSED); 57 | } 58 | 59 | public int getTotalNumberOfFailedScenarios() { 60 | return getNumberOfScenariosWithStatus(Status.FAILED); 61 | } 62 | 63 | public int getTotalNumberOfSkippedScenarios() { 64 | return getNumberOfScenariosWithStatus(Status.SKIPPED); 65 | } 66 | 67 | private int getNumberOfScenariosWithStatus(final Status status) { 68 | return reports.stream().mapToInt( 69 | report -> (int) report.getElements().stream().filter( 70 | element -> element.getStatus().equals(status) 71 | ).count()).sum(); 72 | } 73 | 74 | public long getTotalDuration() { 75 | long totalDurationMicroseconds = 0; 76 | for (Report report : reports) { 77 | totalDurationMicroseconds += report.getTotalDuration(); 78 | } 79 | return totalDurationMicroseconds; 80 | } 81 | 82 | public String getTotalDurationString() { 83 | return CucElasticPluginUtils.convertMicrosecondsToTimeString(getTotalDuration()); 84 | } 85 | 86 | public Tag getTagFilter() { 87 | return tagFilter; 88 | } 89 | 90 | public void setTagFilter(final Tag tagFilter) { 91 | this.tagFilter = tagFilter; 92 | } 93 | 94 | public Feature getFeatureFilter() { 95 | return featureFilter; 96 | } 97 | 98 | public void setFeatureFilter(final Feature featureFilter) { 99 | this.featureFilter = featureFilter; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Real-time reporting of cucumber tests to Elastic Search 4 | 5 | ## Elastic Search and Kibana for Cucumber Reports 6 | 7 | ### Journey and Idea 8 | Very recently, I started learning and doing hands-on on Cucumber in Java and wrote my first Cucumber+TestNG+Selenium Automation Test Framework. I used a couple of third party reporting plugins to generate detailed HTML reports website containing single and/or many web pages. Though I liked the reports and found them useful, yet I needed a better and unified overview of the results which is UI agnostic. 9 | 10 | With the clear requirement in mind, I did some research on internet and talk with colleagues around me. I came across the ELK (Elasticserach, Logstash, Kibana) solution but all 3 were quite new to me, though I installed all three on my machine and started exploring them. Very soon, I figured out that it would be quite a challenge and time consuming to learn and perform the desired result with Logstash and its various plugins. 11 | 12 | Being a Java developer at heart and learning the fact that the 2 of the reporting plugins I used and mentioned above, are using the JSON test result file(s) as input and writing the HTML files as output, I decided to go for the solution same way but change the output destination (Elastic Search not HTML pages). 13 | 14 | With test report data in Elastic Search, I can have a unified dashboard on UI agnostic tools like Kibana/Grafana for all tests with the possibility to filter and analyze the data, extend and share it across teams. 15 | 16 | ### Pros 17 | 1. **Java/Maven solution** - Java has been among top 3 preferred languages in the world for BDD implementation. Hence the solution can easily be plugged in with the system. 18 | 2. **Plug and Play** - With cucumber test result data in Elastic Search, the user can easily plugin any tool/script (Kibana, Grafana, Shell, REST) and play around the test result data. 19 | 3. **UI agnostic** - No static HTML web page(s), hence the control of creation and management of Dashboard fully lies into the hands of the end user using a tool like Kibana. The end users can select what they want to/not to see on their dashboard. 20 | 3. **Continuous Integration** - Can easily be integrated with Jenkins. 21 | 4. **Power of Elastic Search** - Enables the plugin as well as end user to get all the benefits of Elastic Search. 22 | 5. **Power of Kibana** - Enables the end user to apply filter and analyze the data, extend and share it across teams. 23 | 6. **Handy Configurable options** - The plugin user has set of configuration options available to control things like, 24 | - skip reading the Cucumber Test Report(JSON file(s)) and ask the plugin to exit(defaults to false). 25 | - change/overwrite the Hostname of Elastic Search Server (defaults to localhost). 26 | - change/overwrite sending the Feature, Scenario, Step, Tag specific JSON documents to Elastic Search(defaults to false). 27 | - change/overwrite the 'index' and 'document_type' for Feature, Scenario, Step, Tag specific documents for Elastic Search. 28 | 29 | ### Cons 30 | 1. Details of failures like assertion failures/stacktrace (screenshots in case of selenium) not available. 31 | 32 | ### Settings and Usages 33 | [USAGE](USAGE.md) 34 | 35 | ### Kibana Dashboard 36 | 37 | 38 | 39 | 40 | 41 | ### Roadmap 42 | 43 | 1. Give a nice name to the plugin. Ask the colleagues for suggestions. I named it Cucelastic plugin. 44 | 2. Give an icon to the plugin. I made an icon but need some editing. 45 | 4. Publish the plugin on Maven Cenral. 46 | 5. Write Blogs to let the world know and use it. 47 | 6. Discuss and take feedback for future improvement and enhancement. 48 | -------------------------------------------------------------------------------- /src/test/java/com/araj/cucumber/elasticsearch/json/pojo/ResultMatchTest.java: -------------------------------------------------------------------------------- 1 | package com.araj.cucumber.elasticsearch.json.pojo; 2 | 3 | import com.araj.cucumber.elasticsearch.constants.Status; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | import static org.assertj.core.api.Assertions.*; 11 | 12 | public class ResultMatchTest { 13 | private ResultMatch resultMatch; 14 | 15 | @Before 16 | public void setup() { 17 | resultMatch = new ResultMatch(); 18 | } 19 | 20 | @Test 21 | public void glueMethodNameTest() { 22 | Match match = new Match(); 23 | match.setLocation("someMethod"); 24 | resultMatch.setMatch(match); 25 | assertThat("someMethod").isEqualTo(resultMatch.getGlueMethodName()); 26 | } 27 | 28 | @Test 29 | public void argumentsTest() { 30 | Match match = new Match(); 31 | List arguments = new ArrayList<>(); 32 | Argument argument = new Argument(); 33 | argument.setVal("arg1"); 34 | argument.setOffset(10); 35 | arguments.add(argument); 36 | match.setArguments(arguments); 37 | resultMatch.setMatch(match); 38 | assertThat(1).isEqualTo(resultMatch.getArguments().size()); 39 | assertThat("arg1").isEqualTo(resultMatch.getArguments().get(0).getVal()); 40 | assertThat(10).isEqualTo(resultMatch.getArguments().get(0).getOffset()); 41 | } 42 | 43 | @Test 44 | public void getStatusStringTest() { 45 | Result result = new Result(); 46 | result.setStatus(Status.SKIPPED.getStatusAsString()); 47 | resultMatch.setResult(result); 48 | assertThat("skipped").isEqualTo(resultMatch.getStatusString()); 49 | } 50 | 51 | @Test 52 | public void isFailedTest() { 53 | Result result = new Result(); 54 | result.setStatus(Status.FAILED.getStatusAsString()); 55 | resultMatch.setResult(result); 56 | assertThat(resultMatch.isFailed()).isTrue(); 57 | assertThat(resultMatch.isPassed()).isFalse(); 58 | assertThat(resultMatch.isSkipped()).isFalse(); 59 | } 60 | 61 | @Test 62 | public void isPassedTest() { 63 | Result result = new Result(); 64 | result.setStatus(Status.PASSED.getStatusAsString()); 65 | resultMatch.setResult(result); 66 | assertThat(resultMatch.isFailed()).isFalse(); 67 | assertThat(resultMatch.isPassed()).isTrue(); 68 | assertThat(resultMatch.isSkipped()).isFalse(); 69 | } 70 | 71 | @Test 72 | public void isSkippedTest() { 73 | Result result = new Result(); 74 | result.setStatus(Status.SKIPPED.getStatusAsString()); 75 | resultMatch.setResult(result); 76 | assertThat(resultMatch.isFailed()).isFalse(); 77 | assertThat(resultMatch.isPassed()).isFalse(); 78 | assertThat(resultMatch.isSkipped()).isTrue(); 79 | } 80 | 81 | @Test 82 | public void getConsolidatedStatusTest() { 83 | Result result = new Result(); 84 | result.setStatus(Status.SKIPPED.getStatusAsString()); 85 | resultMatch.setResult(result); 86 | assertThat(Status.SKIPPED).isEqualTo(resultMatch.getConsolidatedStatus()); 87 | assertThat("skipped").isEqualTo(resultMatch.getConsolidatedStatusString()); 88 | 89 | result = new Result(); 90 | result.setStatus(Status.PENDING.getStatusAsString()); 91 | resultMatch.setResult(result); 92 | assertThat(Status.SKIPPED).isEqualTo(resultMatch.getConsolidatedStatus()); 93 | assertThat("skipped").isEqualTo(resultMatch.getConsolidatedStatusString()); 94 | 95 | result = new Result(); 96 | result.setStatus(Status.UNDEFINED.getStatusAsString()); 97 | resultMatch.setResult(result); 98 | assertThat(Status.SKIPPED).isEqualTo(resultMatch.getConsolidatedStatus()); 99 | assertThat("skipped").isEqualTo(resultMatch.getConsolidatedStatusString()); 100 | 101 | result = new Result(); 102 | result.setStatus(Status.AMBIGUOUS.getStatusAsString()); 103 | resultMatch.setResult(result); 104 | assertThat(Status.SKIPPED).isEqualTo(resultMatch.getConsolidatedStatus()); 105 | assertThat("skipped").isEqualTo(resultMatch.getConsolidatedStatusString()); 106 | 107 | result = new Result(); 108 | result.setStatus(Status.PASSED.getStatusAsString()); 109 | resultMatch.setResult(result); 110 | assertThat(Status.PASSED).isEqualTo(resultMatch.getConsolidatedStatus()); 111 | assertThat("passed").isEqualTo(resultMatch.getConsolidatedStatusString()); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/test/java/com/araj/cucumber/elasticsearch/json/JsonPojoConverterTest.java: -------------------------------------------------------------------------------- 1 | package com.araj.cucumber.elasticsearch.json; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | import static org.mockito.Mockito.mock; 5 | 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | 9 | import com.araj.cucumber.elasticsearch.constants.Status; 10 | import com.araj.cucumber.elasticsearch.exceptions.CucElasticPluginException; 11 | import com.araj.cucumber.elasticsearch.json.pojo.Element; 12 | import com.araj.cucumber.elasticsearch.json.pojo.Report; 13 | import com.araj.cucumber.elasticsearch.json.postprocessors.ElementPostProcessor; 14 | import com.araj.cucumber.elasticsearch.json.postprocessors.ReportPostProcessor; 15 | 16 | public class JsonPojoConverterTest { 17 | private JsonPojoConverter pojoConverter; 18 | 19 | @Before 20 | public void setup() { 21 | ElementPostProcessor elementPostProcessor = mock(ElementPostProcessor.class); 22 | ReportPostProcessor reportPostProcessor = mock(ReportPostProcessor.class); 23 | pojoConverter = new JsonPojoConverter(reportPostProcessor, elementPostProcessor); 24 | } 25 | 26 | @Test 27 | public void convertEmptyJsonToReportPojosTest() throws CucElasticPluginException { 28 | String json = ""; 29 | Report[] reports = pojoConverter.convertJsonToReportPojos(json); 30 | assertThat(reports).isNull(); 31 | } 32 | 33 | @Test 34 | public void convertJsonToReportPojosTest() throws CucElasticPluginException { 35 | String json = "[\n" + 36 | " {\n" + 37 | " \"line\": 1,\n" + 38 | " \"elements\": [\n" + 39 | " {\n" + 40 | " \"before\": [\n" + 41 | " {\n" + 42 | " \"result\": {\n" + 43 | " \"duration\": 5554929,\n" + 44 | " \"status\": \"passed\"\n" + 45 | " },\n" + 46 | " \"match\": {\n" + 47 | " \"location\": \"BeforeAfterScenario.before(Scenario)\"\n" + 48 | " }\n" + 49 | " }\n" + 50 | " ],\n" + 51 | " \"line\": 5,\n" + 52 | " \"name\": \"Test feature\",\n" + 53 | " \"description\": \"\",\n" + 54 | " \"id\": \"test;id\",\n" + 55 | " \"after\": [\n" + 56 | " {\n" + 57 | " \"result\": {\n" + 58 | " \"duration\": 153270,\n" + 59 | " \"status\": \"passed\"\n" + 60 | " },\n" + 61 | " \"match\": {\n" + 62 | " \"location\": \"BeforeAfterScenario.after(Scenario)\"\n" + 63 | " }\n" + 64 | " }\n" + 65 | " ],\n" + 66 | " \"type\": \"scenario\",\n" + 67 | " \"keyword\": \"Scenario\",\n" + 68 | " \"steps\": [\n" + 69 | " {\n" + 70 | " \"result\": {\n" + 71 | " \"duration\": 12453061125,\n" + 72 | " \"status\": \"passed\"\n" + 73 | " },\n" + 74 | " \"line\": 7,\n" + 75 | " \"name\": \"the start page is opened\",\n" + 76 | " \"match\": {\n" + 77 | " \"location\": \"PageSteps.theStartPageIsOpened()\"\n" + 78 | " },\n" + 79 | " \"keyword\": \"Given \"\n" + 80 | " },\n" + 81 | " {\n" + 82 | " \"result\": {\n" + 83 | " \"duration\": 292465492,\n" + 84 | " \"status\": \"passed\"\n" + 85 | " },\n" + 86 | " \"line\": 8,\n" + 87 | " \"name\": \"I see something\",\n" + 88 | " \"match\": {\n" + 89 | " \"location\": \"SomeSteps.iSeeSomething()\"\n" + 90 | " },\n" + 91 | " \"keyword\": \"Then \"\n" + 92 | " }\n" + 93 | " ],\n" + 94 | " \"tags\": [\n" + 95 | " {\n" + 96 | " \"line\": 3,\n" + 97 | " \"name\": \"@sometag\"\n" + 98 | " },\n" + 99 | " {\n" + 100 | " \"line\": 4,\n" + 101 | " \"name\": \"@someothertag\"\n" + 102 | " }\n" + 103 | " ]\n" + 104 | " }\n" + 105 | " ],\n" + 106 | " \"name\": \"Test\",\n" + 107 | " \"description\": \"\",\n" + 108 | " \"id\": \"test\",\n" + 109 | " \"keyword\": \"Feature\",\n" + 110 | " \"uri\": \"parallel/features/Test.feature\"\n" + 111 | " }\n" + 112 | "]"; 113 | Report[] reports = pojoConverter.convertJsonToReportPojos(json); 114 | assertThat(1).isEqualTo(reports.length); 115 | Report report = reports[0]; 116 | assertThat("Test").isEqualTo(report.getName()); 117 | assertThat("test").isEqualTo(report.getId()); 118 | assertThat(-1).isEqualTo(report.getFeatureIndex()); 119 | assertThat(12751234816L).isEqualTo(report.getTotalDuration()); 120 | assertThat("").isEqualTo(report.getDescription()); 121 | assertThat(1).isEqualTo(report.getLine()); 122 | assertThat("parallel/features/Test.feature").isEqualTo(report.getUri()); 123 | assertThat(1).isEqualTo(report.getElements().size()); 124 | Element element = report.getElements().get(0); 125 | assertThat(Status.PASSED).isEqualTo(element.getStatus()); 126 | assertThat(2).isEqualTo(element.getSteps().size()); 127 | assertThat(1).isEqualTo(element.getBefore().size()); 128 | assertThat(1).isEqualTo(element.getAfter().size()); 129 | assertThat(0).isEqualTo(element.getScenarioIndex()); 130 | assertThat(12751234816L).isEqualTo(element.getTotalDuration()); 131 | assertThat(2).isEqualTo(element.getTotalNumberOfPassedSteps()); 132 | assertThat(0).isEqualTo(element.getTotalNumberOfSkippedSteps()); 133 | assertThat(0).isEqualTo(element.getTotalNumberOfFailedSteps()); 134 | assertThat(4).isEqualTo(element.getAllResultMatches().size()); 135 | } 136 | 137 | @Test(expected = CucElasticPluginException.class) 138 | public void convertJsonToReportPojosInvalidTest() throws CucElasticPluginException { 139 | pojoConverter.convertJsonToReportPojos("@#$%^&"); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.araj.cucumber.elasticsearch 6 | cucelastic-maven-plugin 7 | 1.0 8 | https://github.com/AshisRaj/cucelastic-maven-plugin 9 | 10 | Cucumber Elastic Search Plugin 11 | Plugin for publishing Cucumber Test Result documents in Elastic Search. 12 | 13 | 2018 14 | 15 | 16 | 17 | https://github.com/AshisRaj/ 18 | 19 | 20 | 21 | 22 | Ashis Raj 23 | 24 | 25 | 26 | Developer and Test Automation Engineer 27 | 28 | 29 | 30 | 31 | maven-plugin 32 | 33 | 34 | UTF-8 35 | UTF-8 36 | UTF-8 37 | UTF-8 38 | 39 | 3.5.4 40 | 3.4 41 | 3.6.1 42 | 1.8 43 | 1.8 44 | 3.0.2 45 | 3.5 46 | 2.4 47 | 48 | 2.8.5 49 | 1.8.3 50 | 2.9.7 51 | 1.4.9 52 | 2.23.0 53 | 4.12 54 | 3.11.1 55 | 0.8.10 56 | 57 | 58 | 59 | 60 | org.apache.maven 61 | maven-plugin-api 62 | ${maven.version} 63 | 64 | 65 | org.apache.maven.plugin-tools 66 | maven-plugin-annotations 67 | ${maven.plugin.annotations.version} 68 | provided 69 | 70 | 71 | com.google.code.gson 72 | gson 73 | ${gson.version} 74 | 75 | 76 | io.gsonfire 77 | gson-fire 78 | ${gsonfire.version} 79 | 80 | 81 | 82 | com.fasterxml.jackson.core 83 | jackson-databind 84 | ${jackson.databind.version} 85 | 86 | 87 | com.mashape.unirest 88 | unirest-java 89 | ${unirest.java.version} 90 | 91 | 92 | org.mockito 93 | mockito-core 94 | ${mockito.version} 95 | test 96 | 97 | 98 | com.openpojo 99 | openpojo 100 | ${openpojo.version} 101 | test 102 | 103 | 104 | junit 105 | junit 106 | ${junit.version} 107 | test 108 | 109 | 110 | 111 | org.assertj 112 | assertj-core 113 | ${assertj.version} 114 | test 115 | 116 | 117 | 118 | 119 | 120 | org.apache.maven.plugins 121 | maven-compiler-plugin 122 | ${maven.compiler.plugin.version} 123 | 124 | ${maven.compiler.source} 125 | ${maven.compiler.target} 126 | UTF-8 127 | 128 | 129 | 130 | org.apache.maven.plugins 131 | maven-jar-plugin 132 | ${maven.jar.plugin.version} 133 | 134 | 135 | 136 | true 137 | true 138 | 139 | 140 | 141 | 142 | 143 | org.apache.maven.plugins 144 | maven-source-plugin 145 | ${maven.source.plugin.version} 146 | 147 | 148 | attach-sources 149 | 150 | jar 151 | 152 | 153 | 154 | 155 | 156 | org.apache.maven.plugins 157 | maven-plugin-plugin 158 | ${maven.plugin.plugin.version} 159 | 160 | false 161 | 162 | 163 | 164 | mojo-descriptor 165 | 166 | descriptor 167 | 168 | 169 | 170 | help-descriptor 171 | 172 | helpmojo 173 | 174 | process-classes 175 | 176 | 177 | 178 | 179 | 180 | 181 | -------------------------------------------------------------------------------- /src/main/java/com/araj/cucumber/elasticsearch/json/pojo/Element.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Ashis Raj 3 | */ 4 | 5 | package com.araj.cucumber.elasticsearch.json.pojo; 6 | 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | import com.araj.cucumber.elasticsearch.constants.Status; 12 | import com.araj.cucumber.elasticsearch.utils.CucElasticPluginUtils; 13 | 14 | public class Element { 15 | private List before = new ArrayList<>(); 16 | private int line; 17 | private String name = ""; 18 | private String description = ""; 19 | private String id = ""; 20 | private List after = new ArrayList<>(); 21 | private String type = ""; 22 | private String keyword = ""; 23 | private List steps = new ArrayList<>(); 24 | private List tags = new ArrayList<>(); 25 | 26 | private transient int scenarioIndex = 0; 27 | 28 | public List getTags() { 29 | return tags; 30 | } 31 | 32 | public void setTags(final List tags) { 33 | this.tags = tags; 34 | } 35 | 36 | public List getBefore() { 37 | return before; 38 | } 39 | 40 | public void setBefore(final List before) { 41 | this.before = before; 42 | } 43 | 44 | public int getLine() { 45 | return line; 46 | } 47 | 48 | public void setLine(final int line) { 49 | this.line = line; 50 | } 51 | 52 | public String getName() { 53 | return !name.isEmpty() ? name : "[Unnamed]"; 54 | } 55 | 56 | public void setName(final String name) { 57 | this.name = name; 58 | } 59 | 60 | public String getDescription() { 61 | return description; 62 | } 63 | 64 | public void setDescription(final String description) { 65 | this.description = description; 66 | } 67 | 68 | public String getId() { 69 | return id; 70 | } 71 | 72 | public void setId(final String id) { 73 | this.id = id; 74 | } 75 | 76 | public List getAfter() { 77 | return after; 78 | } 79 | 80 | public void setAfter(final List after) { 81 | this.after = after; 82 | } 83 | 84 | public String getType() { 85 | return type; 86 | } 87 | 88 | public void setType(final String type) { 89 | this.type = type; 90 | } 91 | 92 | public String getKeyword() { 93 | return keyword; 94 | } 95 | 96 | public void setKeyword(final String keyword) { 97 | this.keyword = keyword; 98 | } 99 | 100 | public List getSteps() { 101 | return steps; 102 | } 103 | 104 | public void setSteps(final List steps) { 105 | this.steps = steps; 106 | } 107 | 108 | public boolean isScenario() { 109 | return type.equals("scenario"); 110 | } 111 | 112 | public boolean isFailed() { 113 | return getStatus() == Status.FAILED; 114 | } 115 | 116 | public boolean isPassed() { 117 | return getStatus() == Status.PASSED; 118 | } 119 | 120 | public boolean isSkipped() { 121 | return getStatus() == Status.SKIPPED; 122 | } 123 | 124 | public Status getStatus() { 125 | int totalSteps = steps.size(); 126 | 127 | if (totalSteps == 0) { 128 | return Status.SKIPPED; 129 | } 130 | 131 | // If any hooks fail, report the scenario as failed 132 | for (ResultMatch beforeHook : before) { 133 | if (beforeHook.isFailed()) { 134 | return Status.FAILED; 135 | } 136 | } 137 | for (ResultMatch afterHook : after) { 138 | if (afterHook.isFailed()) { 139 | return Status.FAILED; 140 | } 141 | } 142 | 143 | // If all steps have the same status, return this as the scenario status. 144 | for (Status status : Status.values()) { 145 | long count = 0L; 146 | for (Step step : steps) { 147 | if (step.getStatus() == status) { 148 | count++; 149 | } 150 | 151 | // If any step hooks fail, report scenario as failed. 152 | for (ResultMatch beforeStepHook : step.getBefore()) { 153 | if (beforeStepHook.isFailed()) { 154 | return Status.FAILED; 155 | } 156 | } 157 | for (ResultMatch afterStepHook : step.getAfter()) { 158 | if (afterStepHook.isFailed()) { 159 | return Status.FAILED; 160 | } 161 | } 162 | } 163 | int stepNumber = (int) count; 164 | if (totalSteps == stepNumber) { 165 | if (status != Status.UNDEFINED) { 166 | return status; 167 | } else { 168 | return Status.SKIPPED; 169 | } 170 | } 171 | } 172 | 173 | // If at least one step passed and the other steps are skipped, return passed. 174 | if (getTotalNumberOfPassedSteps() >= 0 && 175 | (getTotalNumberOfSkippedSteps() + getTotalNumberOfPassedSteps()) == getTotalNumberOfSteps()) { 176 | return Status.PASSED; 177 | } 178 | 179 | // If all steps are skipped return skipped. 180 | if (getTotalNumberOfSkippedSteps() == totalSteps) { 181 | return Status.SKIPPED; 182 | } 183 | 184 | return Status.FAILED; 185 | } 186 | 187 | public int getScenarioIndex() { 188 | return scenarioIndex; 189 | } 190 | 191 | public void setScenarioIndex(final int scenarioIndex) { 192 | this.scenarioIndex = scenarioIndex; 193 | } 194 | 195 | public int getTotalNumberOfSteps() { 196 | return getSteps().size(); 197 | } 198 | 199 | public int getTotalNumberOfPassedSteps() { 200 | return getNumberOfStepsWithStatus(Status.PASSED); 201 | } 202 | 203 | public int getTotalNumberOfFailedSteps() { 204 | return getNumberOfStepsWithStatus(Status.FAILED) + 205 | getNumberOfStepsWithStatus(Status.UNDEFINED) + 206 | getNumberOfStepsWithStatus(Status.AMBIGUOUS); 207 | } 208 | 209 | public int getTotalNumberOfSkippedSteps() { 210 | return getNumberOfStepsWithStatus(Status.SKIPPED) + getNumberOfStepsWithStatus(Status.PENDING); 211 | } 212 | 213 | private int getNumberOfStepsWithStatus(final Status status) { 214 | return (int) getSteps().stream().filter(step -> step.getStatus() == status).count(); 215 | } 216 | 217 | public long getTotalDuration() { 218 | long totalDurationMicroseconds = 0; 219 | for (ResultMatch beforeStep : before) { 220 | totalDurationMicroseconds += beforeStep.getResult().getDuration(); 221 | } 222 | for (Step step : steps) { 223 | totalDurationMicroseconds += step.getResult().getDuration(); 224 | } 225 | for (ResultMatch afterStep : after) { 226 | totalDurationMicroseconds += afterStep.getResult().getDuration(); 227 | } 228 | return totalDurationMicroseconds; 229 | } 230 | 231 | public String returnTotalDurationString() { 232 | return CucElasticPluginUtils.convertMicrosecondsToTimeString(getTotalDuration()); 233 | } 234 | 235 | public boolean hasDocStrings() { 236 | for (Step step : steps) { 237 | if (step.getDocString() != null){ 238 | return true; 239 | } 240 | } 241 | return false; 242 | } 243 | 244 | public boolean hasStepHooks() { 245 | for (Step step : steps) { 246 | if (step.getBefore().size() > 0){ 247 | return true; 248 | } 249 | if (step.getAfter().size() > 0){ 250 | return true; 251 | } 252 | } 253 | return false; 254 | } 255 | 256 | public List getAllResultMatches() { 257 | List resultMatches = new ArrayList<>(getBefore()); 258 | resultMatches.addAll(getSteps()); 259 | resultMatches.addAll(getAfter()); 260 | return resultMatches; 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /src/main/java/com/araj/cucumber/elasticsearch/CucElasticPlugin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Ashis Raj 3 | */ 4 | 5 | package com.araj.cucumber.elasticsearch; 6 | 7 | import java.nio.file.Path; 8 | import java.util.List; 9 | 10 | import javax.inject.Inject; 11 | 12 | import org.apache.maven.plugin.AbstractMojo; 13 | import org.apache.maven.plugins.annotations.Mojo; 14 | import org.apache.maven.plugins.annotations.Parameter; 15 | 16 | import com.araj.cucumber.elasticsearch.exceptions.CucElasticPluginException; 17 | import com.araj.cucumber.elasticsearch.filesystem.FileIO; 18 | import com.araj.cucumber.elasticsearch.filesystem.FileSystemManager; 19 | import com.araj.cucumber.elasticsearch.json.JsonPojoConverter; 20 | import com.araj.cucumber.elasticsearch.json.pojo.Report; 21 | import com.araj.cucumber.elasticsearch.logging.CucElasticPluginLogger; 22 | import com.araj.cucumber.elasticsearch.pojos.collections.AllScenariosCollection; 23 | import com.araj.cucumber.elasticsearch.properties.PropertyManager; 24 | import com.araj.cucumber.elasticsearch.utils.CucElasticPluginReportGenerator; 25 | 26 | /** 27 | * The main plugin class. 28 | */ 29 | 30 | @Mojo(name = "load") 31 | public class CucElasticPlugin extends AbstractMojo { 32 | 33 | private final CucElasticPluginLogger logger; 34 | private final PropertyManager propertyManager; 35 | private final FileSystemManager fileSystemManager; 36 | private final FileIO fileIO; 37 | private final JsonPojoConverter jsonPojoConverter; 38 | private final CucElasticPluginReportGenerator reportGenerator; 39 | 40 | /** 41 | * The path to the Cucumber JSON files. 42 | */ 43 | @Parameter(property = "load.sourceJsonReportDirectory", required = true) 44 | private String sourceJsonReportDirectory = ""; 45 | 46 | /** 47 | * The host name of elastic search server. 48 | */ 49 | @Parameter(property = "load.elasticSearchHostName", required = true) 50 | private String elasticSearchHostName = ""; 51 | 52 | /** 53 | * The Cucumber Feature summary index name for elastic search. 54 | */ 55 | @Parameter(property = "load.featureSummaryIndex", defaultValue="feature_summary_index", required = true) 56 | private String featureSummaryIndex = ""; 57 | 58 | /** 59 | * The Cucumber Scenario summary index name for elastic search. 60 | */ 61 | @Parameter(property = "load.scenarioSummaryIndex", defaultValue="scenario_summary_index", required = true) 62 | private String scenarioSummaryIndex = ""; 63 | 64 | /** 65 | * The Cucumber Steps summary index name for elastic search. 66 | */ 67 | @Parameter(property = "load.stepSummaryIndex", defaultValue="step_summary_index", required = true) 68 | private String stepSummaryIndex = ""; 69 | 70 | /** 71 | * The Cucumber Tags summary index name for elastic search. 72 | */ 73 | @Parameter(property = "load.tagSummaryIndex", defaultValue="tag_summary_index", required = true) 74 | private String tagSummaryIndex = ""; 75 | 76 | /** 77 | * The Cucumber Feature summary document type name for elastic search. 78 | */ 79 | @Parameter(property = "load.featureSummaryDocumentType", defaultValue="feature_summary_document_type", required = true) 80 | private String featureSummaryDocumentType = ""; 81 | 82 | /** 83 | * The Cucumber Scenario summary document type name for elastic search. 84 | */ 85 | @Parameter(property = "load.scenarioSummaryDocumentType", defaultValue="scenario_summary_document_type", required = true) 86 | private String scenarioSummaryDocumentType = ""; 87 | 88 | /** 89 | * The Cucumber Steps summary document type name for elastic search. 90 | */ 91 | @Parameter(property = "load.stepSummaryDocumentType", defaultValue="step_summary_document_type", required = true) 92 | private String stepSummaryDocumentType = ""; 93 | 94 | /** 95 | * The Cucumber Tags summary document type name for elastic search. 96 | */ 97 | @Parameter(property = "load.tagSummaryDocumentType", defaultValue="tag_summary_document_type", required = true) 98 | private String tagSummaryDocumentType = ""; 99 | 100 | /** 101 | * The flag to control sending Feature summary documents to elastic search. 102 | */ 103 | @Parameter(property = "load.sendFeatureSummaryToElasticSearch", defaultValue="false", required = true) 104 | private String sendFeatureSummaryToElasticSearch = ""; 105 | 106 | /** 107 | * The flag to control sending Scenario summary documents to elastic search. 108 | */ 109 | @Parameter(property = "load.sendScenarioSummaryToElasticSearch", defaultValue="false", required = true) 110 | private String sendScenarioSummaryToElasticSearch = ""; 111 | 112 | /** 113 | * The flag to control sending Step summary documents to elastic search. 114 | */ 115 | @Parameter(property = "load.sendStepSummaryToElasticSearch", defaultValue="false", required = true) 116 | private String sendStepSummaryToElasticSearch = ""; 117 | 118 | /** 119 | * The flag to control sending Tag summary documents to elastic search. 120 | */ 121 | @Parameter(property = "load.sendTagSummaryToElasticSearch", defaultValue="false", required = true) 122 | private String sendTagSummaryToElasticSearch = ""; 123 | 124 | /** 125 | * Skip Cucumber report generation. 126 | */ 127 | @Parameter(defaultValue = "false", property = "load.skip") 128 | private boolean skip; 129 | 130 | @Inject 131 | public CucElasticPlugin( 132 | final CucElasticPluginLogger logger, 133 | final PropertyManager propertyManager, 134 | final FileSystemManager fileSystemManager, 135 | final FileIO fileIO, 136 | final JsonPojoConverter jsonPojoConverter, 137 | final CucElasticPluginReportGenerator reportGenerator 138 | ) { 139 | this.propertyManager = propertyManager; 140 | this.fileSystemManager = fileSystemManager; 141 | this.fileIO = fileIO; 142 | this.jsonPojoConverter = jsonPojoConverter; 143 | this.logger = logger; 144 | this.reportGenerator = reportGenerator; 145 | } 146 | 147 | /** 148 | * Cucumber Report start method. 149 | * 150 | * @throws CucElasticPluginException When thrown, the plugin execution is stopped. 151 | */ 152 | public void execute() throws CucElasticPluginException { 153 | // Initialize logger to be available outside the AbstractMojo class 154 | logger.setMojoLogger(getLog()); 155 | 156 | if (skip) { 157 | getLog().info("Cucumber report generation was skipped by a configuration flag."); 158 | return; 159 | } 160 | 161 | // Initialize and validate passed pom properties 162 | propertyManager.setSourceJsonReportDirectory(sourceJsonReportDirectory); 163 | propertyManager.setElasticSearchHostName(elasticSearchHostName); 164 | propertyManager.setFeatureSummaryIndex(featureSummaryIndex); 165 | propertyManager.setFeatureSummaryDocumentType(featureSummaryDocumentType); 166 | propertyManager.setScenarioSummaryIndex(scenarioSummaryIndex); 167 | propertyManager.setScenarioSummaryDocumentType(scenarioSummaryDocumentType); 168 | propertyManager.setStepSummaryIndex(stepSummaryIndex); 169 | propertyManager.setStepSummaryDocumentType(stepSummaryDocumentType); 170 | propertyManager.setTagSummaryIndex(tagSummaryIndex); 171 | propertyManager.setTagSummaryDocumentType(tagSummaryDocumentType); 172 | propertyManager.setSendFeatureSummaryToElasticSearch(sendFeatureSummaryToElasticSearch); 173 | propertyManager.setSendScenarioSummaryToElasticSearch(sendScenarioSummaryToElasticSearch); 174 | propertyManager.setSendStepSummaryToElasticSearch(sendStepSummaryToElasticSearch); 175 | propertyManager.setSendTagSummaryToElasticSearch(sendTagSummaryToElasticSearch); 176 | 177 | propertyManager.validateSettings(); 178 | 179 | logger.info("-----------------------------------------------"); 180 | logger.info(String.format(" Cucumber Report Maven Plugin, version %s", getClass().getPackage().getImplementationVersion())); 181 | logger.info("-----------------------------------------------"); 182 | propertyManager.logProperties(); 183 | 184 | AllScenariosCollection allScenariosPageCollection = new AllScenariosCollection(); 185 | List jsonFilePaths = fileSystemManager.getJsonFilePaths(propertyManager.getSourceJsonReportDirectory()); 186 | for (Path jsonFilePath : jsonFilePaths) { 187 | String jsonString = fileIO.readContentFromFile(jsonFilePath.toString()); 188 | try { 189 | Report[] reports = jsonPojoConverter.convertJsonToReportPojos(jsonString); 190 | allScenariosPageCollection.addReports(reports); 191 | } catch (CucElasticPluginException e) { 192 | logger.error("Could not parse JSON in file '" + jsonFilePath.toString() + "': " + e.getMessage()); 193 | } 194 | } 195 | 196 | reportGenerator.generateAndSendReportDocumentsForElasticSearch(allScenariosPageCollection); 197 | } 198 | 199 | } 200 | -------------------------------------------------------------------------------- /src/main/java/com/araj/cucumber/elasticsearch/properties/PropertyManager.java: -------------------------------------------------------------------------------- 1 | package com.araj.cucumber.elasticsearch.properties; 2 | 3 | import javax.inject.Inject; 4 | import javax.inject.Singleton; 5 | 6 | import com.araj.cucumber.elasticsearch.exceptions.CucElasticPluginException; 7 | import com.araj.cucumber.elasticsearch.exceptions.properties.WrongOrMissingPropertyException; 8 | import com.araj.cucumber.elasticsearch.logging.CucElasticPluginLogger; 9 | 10 | @Singleton 11 | public class PropertyManager { 12 | 13 | private final CucElasticPluginLogger logger; 14 | 15 | private String sourceJsonReportDirectory; 16 | 17 | private String elasticSearchHostName; 18 | 19 | private String featureSummaryIndex; 20 | 21 | private String scenarioSummaryIndex; 22 | 23 | private String stepSummaryIndex; 24 | 25 | private String tagSummaryIndex; 26 | 27 | private String featureSummaryDocumentType; 28 | 29 | private String scenarioSummaryDocumentType; 30 | 31 | private String stepSummaryDocumentType; 32 | 33 | private String tagSummaryDocumentType; 34 | 35 | private String sendFeatureSummaryToElasticSearch; 36 | 37 | private String sendScenarioSummaryToElasticSearch; 38 | 39 | private String sendStepSummaryToElasticSearch; 40 | 41 | private String sendTagSummaryToElasticSearch; 42 | 43 | @Inject 44 | public PropertyManager(final CucElasticPluginLogger logger) { 45 | this.logger = logger; 46 | } 47 | 48 | public String getSourceJsonReportDirectory() { 49 | return sourceJsonReportDirectory; 50 | } 51 | 52 | public void setSourceJsonReportDirectory(final String reportDirectory) { 53 | this.sourceJsonReportDirectory = reportDirectory; 54 | } 55 | 56 | public String getElasticSearchHostName() { 57 | return elasticSearchHostName; 58 | } 59 | 60 | public void setElasticSearchHostName(String elasticSearchHostName) { 61 | this.elasticSearchHostName = elasticSearchHostName; 62 | } 63 | 64 | public String getFeatureSummaryIndex() { 65 | return featureSummaryIndex; 66 | } 67 | 68 | public void setFeatureSummaryIndex(String featureSummaryIndex) { 69 | this.featureSummaryIndex = featureSummaryIndex; 70 | } 71 | 72 | public String getScenarioSummaryIndex() { 73 | return scenarioSummaryIndex; 74 | } 75 | 76 | public void setScenarioSummaryIndex(String scenarioSummaryIndex) { 77 | this.scenarioSummaryIndex = scenarioSummaryIndex; 78 | } 79 | 80 | public String getStepSummaryIndex() { 81 | return stepSummaryIndex; 82 | } 83 | 84 | public void setStepSummaryIndex(String stepSummaryIndex) { 85 | this.stepSummaryIndex = stepSummaryIndex; 86 | } 87 | 88 | public String getTagSummaryIndex() { 89 | return tagSummaryIndex; 90 | } 91 | 92 | public void setTagSummaryIndex(String tagSummaryIndex) { 93 | this.tagSummaryIndex = tagSummaryIndex; 94 | } 95 | 96 | public String getFeatureSummaryDocumentType() { 97 | return featureSummaryDocumentType; 98 | } 99 | 100 | public void setFeatureSummaryDocumentType(String featureSummaryDocumentType) { 101 | this.featureSummaryDocumentType = featureSummaryDocumentType; 102 | } 103 | 104 | public String getScenarioSummaryDocumentType() { 105 | return scenarioSummaryDocumentType; 106 | } 107 | 108 | public void setScenarioSummaryDocumentType(String scenarioSummaryDocumentType) { 109 | this.scenarioSummaryDocumentType = scenarioSummaryDocumentType; 110 | } 111 | 112 | public String getStepSummaryDocumentType() { 113 | return stepSummaryDocumentType; 114 | } 115 | 116 | public void setStepSummaryDocumentType(String stepSummaryDocumentType) { 117 | this.stepSummaryDocumentType = stepSummaryDocumentType; 118 | } 119 | 120 | public String getTagSummaryDocumentType() { 121 | return tagSummaryDocumentType; 122 | } 123 | 124 | public void setTagSummaryDocumentType(String tagSummaryDocumentType) { 125 | this.tagSummaryDocumentType = tagSummaryDocumentType; 126 | } 127 | 128 | public String getSendFeatureSummaryToElasticSearch() { 129 | return sendFeatureSummaryToElasticSearch; 130 | } 131 | 132 | public void setSendFeatureSummaryToElasticSearch(String sendFeatureSummaryToElasticSearch) { 133 | this.sendFeatureSummaryToElasticSearch = sendFeatureSummaryToElasticSearch; 134 | } 135 | 136 | public String getSendScenarioSummaryToElasticSearch() { 137 | return sendScenarioSummaryToElasticSearch; 138 | } 139 | 140 | public void setSendScenarioSummaryToElasticSearch(String sendScenarioSummaryToElasticSearch) { 141 | this.sendScenarioSummaryToElasticSearch = sendScenarioSummaryToElasticSearch; 142 | } 143 | 144 | public String getSendStepSummaryToElasticSearch() { 145 | return sendStepSummaryToElasticSearch; 146 | } 147 | 148 | public void setSendStepSummaryToElasticSearch(String sendStepSummaryToElasticSearch) { 149 | this.sendStepSummaryToElasticSearch = sendStepSummaryToElasticSearch; 150 | } 151 | 152 | public String getSendTagSummaryToElasticSearch() { 153 | return sendTagSummaryToElasticSearch; 154 | } 155 | 156 | public void setSendTagSummaryToElasticSearch(String sendTagSummaryToElasticSearch) { 157 | this.sendTagSummaryToElasticSearch = sendTagSummaryToElasticSearch; 158 | } 159 | 160 | /** 161 | * Checks the pom settings for the plugin. 162 | * 163 | * @throws CluecumberPluginException Thrown when a required setting 164 | * is not specified in the pom. 165 | */ 166 | public void validateSettings() throws CucElasticPluginException { 167 | String missingProperty = null; 168 | 169 | if (sourceJsonReportDirectory == null || sourceJsonReportDirectory.equals("")) { 170 | missingProperty = "sourceJsonReportDirectory"; 171 | } else if (elasticSearchHostName == null || elasticSearchHostName.equals("")) { 172 | missingProperty = "elasticSearchHostName"; 173 | } else if (featureSummaryIndex == null || featureSummaryIndex.equals("")) { 174 | missingProperty = "featureSummaryIndex"; 175 | } else if (scenarioSummaryIndex == null || scenarioSummaryIndex.equals("")) { 176 | missingProperty = "scenarioSummaryIndex"; 177 | } else if (stepSummaryIndex == null || stepSummaryIndex.equals("")) { 178 | missingProperty = "stepsSummaryIndex"; 179 | } else if (tagSummaryIndex == null || tagSummaryIndex.equals("")) { 180 | missingProperty = "tagsSummaryIndex"; 181 | } else if (featureSummaryDocumentType == null || featureSummaryDocumentType.equals("")) { 182 | missingProperty = "featureSummaryDocumentType"; 183 | } else if (scenarioSummaryDocumentType == null || scenarioSummaryDocumentType.equals("")) { 184 | missingProperty = "scenarioSummaryDocumentType"; 185 | } else if (stepSummaryDocumentType == null || stepSummaryDocumentType.equals("")) { 186 | missingProperty = "stepSummaryDocumentType"; 187 | } else if (tagSummaryDocumentType == null || tagSummaryDocumentType.equals("")) { 188 | missingProperty = "tagSummaryDocumentType"; 189 | } else if (sendFeatureSummaryToElasticSearch == null || sendFeatureSummaryToElasticSearch.equals("")) { 190 | missingProperty = "sendFeatureSummaryToElasticSearch"; 191 | } else if (sendScenarioSummaryToElasticSearch == null || sendScenarioSummaryToElasticSearch.equals("")) { 192 | missingProperty = "sendScenarioSummaryToElasticSearch"; 193 | } else if (sendStepSummaryToElasticSearch == null || sendStepSummaryToElasticSearch.equals("")) { 194 | missingProperty = "sendStepSummaryToElasticSearch"; 195 | } else if (sendTagSummaryToElasticSearch == null || sendTagSummaryToElasticSearch.equals("")) { 196 | missingProperty = "sendTagSummaryToElasticSearch"; 197 | } 198 | 199 | if (missingProperty != null) { 200 | throw new WrongOrMissingPropertyException(missingProperty); 201 | } 202 | } 203 | 204 | public void logProperties() { 205 | logger.info("- source JSON report directory : " + sourceJsonReportDirectory); 206 | logger.info("- elastic Search Host Name : " + elasticSearchHostName); 207 | logger.info("- feature Summary Index Name : " + featureSummaryIndex); 208 | logger.info("- scenario Summary Index Name : " + scenarioSummaryIndex); 209 | logger.info("- step Summary Index Name : " + stepSummaryIndex); 210 | logger.info("- tag Summary Index Name : " + tagSummaryIndex); 211 | logger.info("- feature Summary Document Type Name : " + featureSummaryDocumentType); 212 | logger.info("- scenario Summary Document Type Name : " + scenarioSummaryDocumentType); 213 | logger.info("- step Summary Document Type Name : " + stepSummaryDocumentType); 214 | logger.info("- tag Summary Document Type Name : " + tagSummaryDocumentType); 215 | logger.info("- send Feature Summary To Elastic Search : " + sendFeatureSummaryToElasticSearch); 216 | logger.info("- send Scenario Summary To Elastic Search : " + sendScenarioSummaryToElasticSearch); 217 | logger.info("- send Step Summary To Elastic Search : " + sendStepSummaryToElasticSearch); 218 | logger.info("- send Tag Summary To Elastic Search : " + sendTagSummaryToElasticSearch); 219 | 220 | logger.info("------------------------------------------------------------------------"); 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /src/test/java/com/araj/cucumber/elasticsearch/json/pojo/ElementTest.java: -------------------------------------------------------------------------------- 1 | package com.araj.cucumber.elasticsearch.json.pojo; 2 | 3 | import com.araj.cucumber.elasticsearch.constants.Status; 4 | 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | import static org.assertj.core.api.Assertions.*; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | public class ElementTest { 13 | private Element element; 14 | 15 | @Before 16 | public void setup() { 17 | element = new Element(); 18 | } 19 | 20 | @Test 21 | public void getSkippedStatusInEmptyElementsTest() { 22 | Status status = element.getStatus(); 23 | assertThat(Status.SKIPPED).isEqualByComparingTo(status); 24 | assertThat(element.isSkipped()).isTrue(); 25 | } 26 | 27 | @Test 28 | public void getPassedStatusTest() { 29 | List steps = new ArrayList<>(); 30 | Step step = new Step(); 31 | Result result = new Result(); 32 | result.setStatus("passed"); 33 | step.setResult(result); 34 | steps.add(step); 35 | element.setSteps(steps); 36 | 37 | Status status = element.getStatus(); 38 | assertThat(Status.PASSED).isEqualByComparingTo(status); 39 | assertThat(element.isPassed()).isTrue(); 40 | } 41 | 42 | @Test 43 | public void passedStatusOnPassedAndSkippedStepsTest() { 44 | List steps = new ArrayList<>(); 45 | Step step = new Step(); 46 | Result result = new Result(); 47 | result.setStatus("passed"); 48 | step.setResult(result); 49 | steps.add(step); 50 | 51 | step = new Step(); 52 | result = new Result(); 53 | result.setStatus("skipped"); 54 | step.setResult(result); 55 | steps.add(step); 56 | element.setSteps(steps); 57 | 58 | Status status = element.getStatus(); 59 | assertThat(Status.PASSED).isEqualByComparingTo(status); 60 | } 61 | 62 | @Test 63 | public void failedStatusOnFailedBeforeHookTest() { 64 | List before = new ArrayList<>(); 65 | ResultMatch beforeHook = new ResultMatch(); 66 | Result beforeHookResult = new Result(); 67 | beforeHookResult.setStatus("failed"); 68 | beforeHook.setResult(beforeHookResult); 69 | before.add(beforeHook); 70 | element.setBefore(before); 71 | 72 | List steps = new ArrayList<>(); 73 | Step step = new Step(); 74 | Result result = new Result(); 75 | result.setStatus("passed"); 76 | step.setResult(result); 77 | steps.add(step); 78 | 79 | step = new Step(); 80 | result = new Result(); 81 | result.setStatus("passed"); 82 | step.setResult(result); 83 | steps.add(step); 84 | element.setSteps(steps); 85 | 86 | Status status = element.getStatus(); 87 | assertThat(Status.FAILED).isEqualByComparingTo(status); 88 | 89 | } 90 | 91 | @Test 92 | public void failedStatusOnFailedAfterHookTest() { 93 | List after = new ArrayList<>(); 94 | ResultMatch afterHook = new ResultMatch(); 95 | Result afterHookResult = new Result(); 96 | afterHookResult.setStatus("failed"); 97 | afterHook.setResult(afterHookResult); 98 | after.add(afterHook); 99 | element.setAfter(after); 100 | 101 | List steps = new ArrayList<>(); 102 | Step step = new Step(); 103 | Result result = new Result(); 104 | result.setStatus("passed"); 105 | step.setResult(result); 106 | steps.add(step); 107 | 108 | step = new Step(); 109 | result = new Result(); 110 | result.setStatus("passed"); 111 | step.setResult(result); 112 | steps.add(step); 113 | element.setSteps(steps); 114 | 115 | Status status = element.getStatus(); 116 | assertThat(Status.FAILED).isEqualByComparingTo(status); 117 | } 118 | 119 | @Test 120 | public void failedStatusOnFailedAfterStepHookTest() { 121 | List steps = new ArrayList<>(); 122 | Step step = new Step(); 123 | Result result = new Result(); 124 | result.setStatus("passed"); 125 | step.setResult(result); 126 | 127 | List after = new ArrayList<>(); 128 | ResultMatch afterStepHook = new ResultMatch(); 129 | Result afterStepHookResult = new Result(); 130 | afterStepHookResult.setStatus("failed"); 131 | afterStepHook.setResult(afterStepHookResult); 132 | after.add(afterStepHook); 133 | step.setAfter(after); 134 | 135 | steps.add(step); 136 | 137 | step = new Step(); 138 | result = new Result(); 139 | result.setStatus("passed"); 140 | step.setResult(result); 141 | steps.add(step); 142 | element.setSteps(steps); 143 | 144 | Status status = element.getStatus(); 145 | assertThat(Status.FAILED).isEqualByComparingTo(status); 146 | } 147 | 148 | @Test 149 | public void failedStatusOnFailedBeforeStepHookTest() { 150 | List steps = new ArrayList<>(); 151 | Step step = new Step(); 152 | Result result = new Result(); 153 | result.setStatus("passed"); 154 | step.setResult(result); 155 | 156 | List before = new ArrayList<>(); 157 | ResultMatch beforeStepHook = new ResultMatch(); 158 | Result beforeStepHookResult = new Result(); 159 | beforeStepHookResult.setStatus("failed"); 160 | step.setResult(beforeStepHookResult); 161 | before.add(beforeStepHook); 162 | step.setBefore(before); 163 | 164 | steps.add(step); 165 | 166 | step = new Step(); 167 | result = new Result(); 168 | result.setStatus("passed"); 169 | step.setResult(result); 170 | steps.add(step); 171 | element.setSteps(steps); 172 | 173 | Status status = element.getStatus(); 174 | assertThat(Status.FAILED).isEqualByComparingTo(status); 175 | } 176 | 177 | @Test 178 | public void getFailedStatusTest() { 179 | List steps = new ArrayList<>(); 180 | Step step = new Step(); 181 | Result result = new Result(); 182 | result.setStatus("failed"); 183 | step.setResult(result); 184 | steps.add(step); 185 | element.setSteps(steps); 186 | 187 | Status status = element.getStatus(); 188 | assertThat(Status.FAILED).isEqualByComparingTo(status); 189 | assertThat(element.isFailed()).isTrue(); 190 | } 191 | 192 | @Test 193 | public void getUndefinedStatusTest() { 194 | List steps = new ArrayList<>(); 195 | Step step = new Step(); 196 | Result result = new Result(); 197 | result.setStatus("undefined"); 198 | step.setResult(result); 199 | steps.add(step); 200 | element.setSteps(steps); 201 | 202 | Status status = element.getStatus(); 203 | assertThat(Status.SKIPPED).isEqualByComparingTo(status); 204 | assertThat(element.isSkipped()).isTrue(); 205 | } 206 | 207 | @Test 208 | public void totalDurationTest() { 209 | List beforeSteps = new ArrayList<>(); 210 | ResultMatch before = new ResultMatch(); 211 | Result beforeResult = new Result(); 212 | beforeResult.setDuration(1000000); 213 | before.setResult(beforeResult); 214 | beforeSteps.add(before); 215 | element.setBefore(beforeSteps); 216 | 217 | List steps = new ArrayList<>(); 218 | Step step = new Step(); 219 | Result stepResult = new Result(); 220 | stepResult.setDuration(9991000003L); 221 | step.setResult(stepResult); 222 | steps.add(step); 223 | 224 | Step step2 = new Step(); 225 | Result stepResult2 = new Result(); 226 | stepResult2.setDuration(123667782L); 227 | step2.setResult(stepResult2); 228 | steps.add(step2); 229 | 230 | element.setSteps(steps); 231 | 232 | List afterSteps = new ArrayList<>(); 233 | ResultMatch after = new ResultMatch(); 234 | Result afterResult = new Result(); 235 | afterResult.setDuration(2000000); 236 | after.setResult(afterResult); 237 | afterSteps.add(after); 238 | element.setAfter(afterSteps); 239 | 240 | assertThat(10117667785L).isEqualTo(element.getTotalDuration()); 241 | assertThat("0m 10s 117ms").isEqualTo(element.returnTotalDurationString()); 242 | } 243 | 244 | @Test 245 | public void stepSummaryTest() { 246 | List steps = new ArrayList<>(); 247 | 248 | Step step1 = new Step(); 249 | Result result1 = new Result(); 250 | result1.setStatus("passed"); 251 | step1.setResult(result1); 252 | steps.add(step1); 253 | steps.add(step1); 254 | steps.add(step1); 255 | 256 | Step step2 = new Step(); 257 | Result result2 = new Result(); 258 | result2.setStatus("skipped"); 259 | step2.setResult(result2); 260 | steps.add(step2); 261 | 262 | Step step3 = new Step(); 263 | Result result3 = new Result(); 264 | result3.setStatus("pending"); 265 | step3.setResult(result3); 266 | steps.add(step3); 267 | 268 | Step step4 = new Step(); 269 | Result result4 = new Result(); 270 | result4.setStatus("failed"); 271 | step4.setResult(result4); 272 | steps.add(step4); 273 | 274 | element.setSteps(steps); 275 | 276 | assertThat(6).isEqualTo(element.getTotalNumberOfSteps()); 277 | assertThat(3).isEqualTo(element.getTotalNumberOfPassedSteps()); 278 | assertThat(1).isEqualTo(element.getTotalNumberOfFailedSteps()); 279 | assertThat(2).isEqualTo(element.getTotalNumberOfSkippedSteps()); 280 | } 281 | 282 | @Test 283 | public void hasDocStringsTest(){ 284 | assertThat(element.hasDocStrings()).isFalse(); 285 | 286 | List steps = new ArrayList<>(); 287 | 288 | Step step1 = new Step(); 289 | steps.add(step1); 290 | 291 | Step step2 = new Step(); 292 | step2.setDocString(new DocString()); 293 | steps.add(step2); 294 | 295 | element.setSteps(steps); 296 | 297 | assertThat(element.hasDocStrings()).isTrue(); 298 | } 299 | 300 | @Test 301 | public void hasStepHooksBeforeTest(){ 302 | assertThat(element.hasStepHooks()).isFalse(); 303 | 304 | List steps = new ArrayList<>(); 305 | 306 | Step step1 = new Step(); 307 | steps.add(step1); 308 | 309 | Step step2 = new Step(); 310 | List beforeStepHooks = new ArrayList<>(); 311 | beforeStepHooks.add(new ResultMatch()); 312 | step2.setBefore(beforeStepHooks); 313 | steps.add(step2); 314 | 315 | element.setSteps(steps); 316 | 317 | assertThat(element.hasStepHooks()).isTrue(); 318 | } 319 | 320 | @Test 321 | public void hasStepHooksAfterTest(){ 322 | assertThat(element.hasStepHooks()).isFalse(); 323 | 324 | List steps = new ArrayList<>(); 325 | 326 | Step step1 = new Step(); 327 | steps.add(step1); 328 | 329 | Step step2 = new Step(); 330 | List afterStepHooks = new ArrayList<>(); 331 | afterStepHooks.add(new ResultMatch()); 332 | step2.setAfter(afterStepHooks); 333 | steps.add(step2); 334 | 335 | element.setSteps(steps); 336 | 337 | assertThat(element.hasStepHooks()).isTrue(); 338 | } 339 | } 340 | -------------------------------------------------------------------------------- /src/main/java/com/araj/cucumber/elasticsearch/utils/CucElasticPluginReportGenerator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Ashis Raj 3 | */ 4 | 5 | package com.araj.cucumber.elasticsearch.utils; 6 | 7 | import java.time.LocalDateTime; 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | import java.util.Map; 11 | 12 | import javax.inject.Inject; 13 | import javax.inject.Singleton; 14 | 15 | import com.araj.cucumber.elasticsearch.constants.Status; 16 | import com.araj.cucumber.elasticsearch.exceptions.CucElasticPluginException; 17 | import com.araj.cucumber.elasticsearch.json.pojo.Element; 18 | import com.araj.cucumber.elasticsearch.json.pojo.Report; 19 | import com.araj.cucumber.elasticsearch.json.pojo.Tag; 20 | import com.araj.cucumber.elasticsearch.logging.CucElasticPluginLogger; 21 | import com.araj.cucumber.elasticsearch.pojos.Feature; 22 | import com.araj.cucumber.elasticsearch.pojos.FeatureSummary; 23 | import com.araj.cucumber.elasticsearch.pojos.ResultCount; 24 | import com.araj.cucumber.elasticsearch.pojos.ResultSender; 25 | import com.araj.cucumber.elasticsearch.pojos.ScenarioSummary; 26 | import com.araj.cucumber.elasticsearch.pojos.StepSummary; 27 | import com.araj.cucumber.elasticsearch.pojos.TagSummary; 28 | import com.araj.cucumber.elasticsearch.pojos.collections.AllFeaturesCollection; 29 | import com.araj.cucumber.elasticsearch.pojos.collections.AllScenariosCollection; 30 | import com.araj.cucumber.elasticsearch.pojos.collections.AllTagsCollection; 31 | import com.araj.cucumber.elasticsearch.properties.PropertyManager; 32 | 33 | @Singleton 34 | public class CucElasticPluginReportGenerator { 35 | 36 | private final CucElasticPluginLogger logger; 37 | private final PropertyManager propertyManager; 38 | private final ResultSender resultSender; 39 | 40 | @Inject 41 | public CucElasticPluginReportGenerator(final CucElasticPluginLogger logger, 42 | final PropertyManager propertyManager, 43 | final ResultSender resultSender) { 44 | this.logger = logger; 45 | this.propertyManager = propertyManager; 46 | this.resultSender = resultSender; 47 | } 48 | 49 | public void generateAndSendReportDocumentsForElasticSearch( 50 | final AllScenariosCollection allScenariosCollection) 51 | throws CucElasticPluginException { 52 | 53 | if("true".equalsIgnoreCase(propertyManager.getSendFeatureSummaryToElasticSearch())) { 54 | // Feature Summary docs 55 | List featureSummaries = 56 | generateFeatureDocuments(allScenariosCollection); 57 | String url = String.format("http://%s/%s/%s", 58 | propertyManager.getElasticSearchHostName(), 59 | propertyManager.getFeatureSummaryIndex(), 60 | propertyManager.getFeatureSummaryDocumentType()); 61 | logger.info("-----------------------------------------------------------"); 62 | logger.info("Sending the FeatureSummary documnets to elastic search"); 63 | resultSender.sendTOElasticSearch(featureSummaries, url); 64 | } else { 65 | logger.info("Sending FeatureSummary documents to elastic search" 66 | + " is disabled\n\tCheck in your pom file for setting of" 67 | + " configuration parameter 'sendFeatureSummaryToElasticSearch'."); 68 | } 69 | if("true".equalsIgnoreCase(propertyManager.getSendScenarioSummaryToElasticSearch())) { 70 | // Scenario Summary docs 71 | List scenarioSummaries = 72 | generateScenarioSummaryDocuments(allScenariosCollection); 73 | logger.info("-----------------------------------------------------------"); 74 | logger.info("Sending the ScenarioSummary documents to elastic search"); 75 | String url = String.format("http://%s/%s/%s", 76 | propertyManager.getElasticSearchHostName(), 77 | propertyManager.getScenarioSummaryIndex(), 78 | propertyManager.getScenarioSummaryDocumentType()); 79 | resultSender.sendTOElasticSearch(scenarioSummaries, url); 80 | } else { 81 | logger.info("Sending ScenarioSummary documents to elastic search" 82 | + " is disabled\n\tCheck in your pom file for setting of" 83 | + " configuration parameter 'sendScenarioSummaryToElasticSearch'."); 84 | } 85 | if("true".equalsIgnoreCase(propertyManager.getSendStepSummaryToElasticSearch())) { 86 | // Step Summary docs 87 | List stepSummaries = 88 | generateStepSummaryDocuments(allScenariosCollection); 89 | logger.info("-----------------------------------------------------------"); 90 | logger.info("Sending the StepSummary documents to elastic search"); 91 | String url = String.format("http://%s/%s/%s", 92 | propertyManager.getElasticSearchHostName(), 93 | propertyManager.getStepSummaryIndex(), 94 | propertyManager.getStepSummaryDocumentType()); 95 | 96 | resultSender.sendTOElasticSearch(stepSummaries, url); 97 | } 98 | else { 99 | logger.info("Sending StepSummary documents to elastic search" 100 | + " is disabled\n\tCheck in your pom file for setting of" 101 | + " configuration parameter 'sendStepSummaryToElasticSearch'."); 102 | } 103 | if("true".equalsIgnoreCase(propertyManager.getSendTagSummaryToElasticSearch())) { 104 | // Tag Summary docs 105 | List tagSummaries = 106 | generateTagSummaryDocuments(allScenariosCollection); 107 | logger.info("-----------------------------------------------------------"); 108 | logger.info("Sending the TagSummary documents to elastic search"); 109 | String url = String.format("http://%s/%s/%s", 110 | propertyManager.getElasticSearchHostName(), 111 | propertyManager.getTagSummaryIndex(), 112 | propertyManager.getTagSummaryDocumentType()); 113 | 114 | resultSender.sendTOElasticSearch(tagSummaries, url); 115 | } 116 | else { 117 | logger.info("Sending TagSummary documents to elastic search" 118 | + " is disabled\n\tCheck in your pom file for setting of" 119 | + " configuration parameter 'sendTagSummaryToElasticSearch'."); 120 | } 121 | } 122 | 123 | 124 | /** 125 | * Generate Documents for features. 126 | * 127 | * @param allScenariosCollection The {@link AllScenariosCollection}. 128 | * @throws CucElasticPluginException The {@link CucElasticPluginException}. 129 | * @return a List of Feature documents 130 | */ 131 | private List generateFeatureDocuments( 132 | final AllScenariosCollection allScenariosCollection) 133 | throws CucElasticPluginException { 134 | List featureSummaries = new ArrayList<>(); 135 | 136 | // Feature summary collection 137 | AllFeaturesCollection allFeaturesCollection = 138 | new AllFeaturesCollection(allScenariosCollection.getReports()); 139 | 140 | // Feature summary documents 141 | for (Map.Entry entry : 142 | allFeaturesCollection.getFeatureResultCounts().entrySet()) { 143 | FeatureSummary featureSummary = new FeatureSummary(); 144 | featureSummary.setfeatureIndex(entry.getKey().getIndex()); 145 | featureSummary.setfeatureName(entry.getKey().getName()); 146 | featureSummary.setTotalScenarios(entry.getValue().getTotal()); 147 | featureSummary.setPassedScenarios(entry.getValue().getPassed()); 148 | featureSummary.setFailedScenarios(entry.getValue().getFailed()); 149 | featureSummary.setSkippedScenarios(entry.getValue().getSkipped()); 150 | featureSummary.setStatus( 151 | entry.getValue().getFailed() > 0 ? 152 | Status.FAILED.getStatusAsString() : 153 | Status.PASSED.getStatusAsString()); 154 | featureSummary.setDate(LocalDateTime.now().toString()); 155 | featureSummaries.add(featureSummary); 156 | } 157 | return featureSummaries; 158 | } 159 | 160 | /** 161 | * Generate Documents for scenarios. 162 | * 163 | * @param allScenariosCollection The {@link AllScenariosCollection}. 164 | * @throws CucElasticPluginException The {@link CucElasticPluginException}. 165 | * @return a List of Scenario documents 166 | */ 167 | private List generateScenarioSummaryDocuments( 168 | final AllScenariosCollection allScenariosCollection) 169 | throws CucElasticPluginException { 170 | List scenarioSummaries = new ArrayList<>(); 171 | for(Report report : allScenariosCollection.getReports()) { 172 | for(Element element : report.getElements()) { 173 | ScenarioSummary scenarioSummary = new ScenarioSummary(); 174 | scenarioSummary.setFeatureIndex(report.getFeatureIndex()); 175 | scenarioSummary.setFeatureName(report.getName()); 176 | scenarioSummary.setScenarioIndex(element.getScenarioIndex()); 177 | scenarioSummary.setscenarioName(element.getName()); 178 | scenarioSummary.setStatus(element.getStatus().getStatusAsString()); 179 | scenarioSummary.setDate(LocalDateTime.now().toString()); 180 | scenarioSummaries.add(scenarioSummary); 181 | } 182 | } 183 | return scenarioSummaries; 184 | } 185 | 186 | /** 187 | * Generate Documents from steps 188 | * 189 | * @param allScenariosCollection The {@link AllScenariosCollection}. 190 | * @throws CucElasticPluginException The {@link CucElasticPluginException}. 191 | * @return a List of Steps documents 192 | */ 193 | private List generateStepSummaryDocuments( 194 | final AllScenariosCollection allScenariosCollection) 195 | throws CucElasticPluginException { 196 | List stepSummaries = new ArrayList<>(); 197 | for(Report report : allScenariosCollection.getReports()) { 198 | for(Element element : report.getElements()) { 199 | StepSummary stepSummary = new StepSummary(); 200 | stepSummary.setScenarioIndex(element.getScenarioIndex()); 201 | stepSummary.setscenarioName(element.getName()); 202 | stepSummary.setTotalSteps(element.getTotalNumberOfSteps()); 203 | stepSummary.setPassedSteps(element.getTotalNumberOfPassedSteps()); 204 | stepSummary.setFailedSteps(element.getTotalNumberOfFailedSteps()); 205 | stepSummary.setSkippedSteps(element.getTotalNumberOfSkippedSteps()); 206 | stepSummary.setDate(LocalDateTime.now().toString()); 207 | stepSummaries.add(stepSummary); 208 | } 209 | } 210 | return stepSummaries; 211 | } 212 | 213 | /** 214 | * Generate Documents for tags. 215 | * 216 | * @param allScenariosCollection The {@link AllScenariosCollection}. 217 | * @throws CluecumberException The {@link CucElasticPluginException}. 218 | */ 219 | private List generateTagSummaryDocuments( 220 | final AllScenariosCollection allScenariosCollection) 221 | throws CucElasticPluginException { 222 | 223 | List tagSummaries = new ArrayList<>(); 224 | 225 | AllTagsCollection allTagsCollection = 226 | new AllTagsCollection(allScenariosCollection.getReports()); 227 | 228 | // Tag summary 229 | for (Map.Entry entry : 230 | allTagsCollection.getTagResultCounts().entrySet()) { 231 | TagSummary tagSummary = new TagSummary(); 232 | tagSummary.setTagName(entry.getKey().getName()); 233 | tagSummary.setPassedScenarios(entry.getValue().getPassed()); 234 | tagSummary.setFailedScenarios(entry.getValue().getFailed()); 235 | tagSummary.setSkippedScenarios(entry.getValue().getSkipped()); 236 | tagSummary.setTotalScenarios(entry.getValue().getTotal()); 237 | tagSummary.setDate(LocalDateTime.now().toString()); 238 | tagSummaries.add(tagSummary); 239 | } 240 | return tagSummaries; 241 | } 242 | } 243 | 244 | -------------------------------------------------------------------------------- /LICENCE.md: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | --------------------------------------------------------------------------------