/* * The MIT License * * Copyright (c) 2013, Cisco Systems, Inc., a California corporation * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.jenkinsci.plugins.cucumber.jsontestsupport; import gherkin.formatter.Formatter; import gherkin.formatter.Reporter; import gherkin.formatter.model.Background; import gherkin.formatter.model.Examples; import gherkin.formatter.model.Feature; import gherkin.formatter.model.Match; import gherkin.formatter.model.Result; import gherkin.formatter.model.Scenario; import gherkin.formatter.model.ScenarioOutline; import gherkin.formatter.model.Step; import gherkin.formatter.model.Tag; import hudson.model.TaskListener; import java.io.File; import java.io.IOException; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; /** * The implementation that gets called back by the Gherkin parser. * * @author James Nord */ class GherkinCallback implements Formatter, Reporter { private static final Logger LOG = Logger.getLogger(GherkinCallback.class.getName()); private boolean ignoreBadSteps = false; private TaskListener listener = null; private FeatureResult currentFeatureResult = null; private ScenarioResult currentScenarioResult = null; private BackgroundResult currentBackground = null; private Step currentStep = null; private Match currentMatch = null; private String currentURI = null; private CucumberTestResult testResult; GherkinCallback(CucumberTestResult testResult) { this.testResult = testResult; } GherkinCallback(CucumberTestResult testResult, TaskListener listener, boolean ignoreBadSteps){ this(testResult); this.listener = listener; this.ignoreBadSteps = ignoreBadSteps; } // Formatter implementation // called before a feature to identify the feature public void uri(String uri) { LOG.log(Level.FINE, "URI: {0}", uri); if (currentURI != null) { LOG.log(Level.SEVERE, "URI received before previous uri handled"); throw new CucumberModelException("URI received before previous uri handled"); } currentURI = uri; } public void feature(Feature feature) { if (LOG.isLoggable(Level.FINE)) { LOG.log(Level.FINE, "Feature: " + feature.getKeyword() + feature.getName()); List<Tag> tags = feature.getTags(); for (Tag tag : tags) { LOG.log(Level.FINE, " " + tag.getName()); } LOG.log(Level.FINE, " " + feature.getDescription()); } // a new feature being received signals the end of the previous feature currentFeatureResult = new FeatureResult(currentURI, feature); currentURI = null; testResult.addFeatureResult(currentFeatureResult); } // applies to a scenario public void background(Background background) { LOG.log(Level.FINE, "Background: {0}", background.getName()); if (currentBackground != null) { LOG.log(Level.SEVERE, "Background: {" + background.getName() + "} received before previous background: {" + currentBackground.getName()+ "} handled"); throw new CucumberModelException("Background: {" + background.getName() + "} received before previous background: {" + currentBackground.getName()+ "} handled"); } currentBackground = new BackgroundResult(background); } public void scenario(Scenario scenario) { if (LOG.isLoggable(Level.FINE)) { LOG.log(Level.FINE, "Scenario: " + scenario.getKeyword() + " " + scenario.getName()); List<Tag> tags = scenario.getTags(); for (Tag tag : tags) { LOG.log(Level.FINE, " " + tag.getName()); } LOG.log(Level.FINE, " " + scenario.getDescription()); LOG.log(Level.FINE, " " + scenario.getComments()); } // a new scenario signifies that the previous scenario has been handled. currentScenarioResult = new ScenarioResult(scenario, currentBackground); currentBackground = null; currentFeatureResult.addScenarioResult(currentScenarioResult); } // appears to not be called. public void scenarioOutline(ScenarioOutline scenarioOutline) { LOG.log(Level.FINE, "ScenarioOutline: {0}", scenarioOutline.getName()); } // appears to not be called. public void examples(Examples examples) { // not stored in the json - used in the Gherkin only LOG.log(Level.FINE, "Examples: {0}", examples.getName()); } // appears to not be called. public void startOfScenarioLifeCycle(Scenario scenario) { LOG.log(Level.FINE, "startOfScenarioLifeCycle: {0}", scenario.getName()); } // appears to not be called. public void endOfScenarioLifeCycle(Scenario scenario) { LOG.log(Level.FINE, "endOfScenarioLifeCycle: {0}", scenario.getName()); } // A step has been called - could be in a background or a Scenario public void step(Step step) { if (LOG.isLoggable(Level.FINE)) { LOG.log(Level.FINE, "Step: " + step.getKeyword() + " " + step.getName()); LOG.log(Level.FINE, " " + step.getRows()); // logger.fine(" " + step.getStackTraceElement()); } if (currentStep != null) { String error = "Step: {" + step.getKeyword() + "} name: {" + step.getName() + "} received before previous step: {" + step.getKeyword() + "} name: {" + step.getName() + "} handled! Maybe caused by broken JSON, see #JENKINS-21835"; listener.error(error); LOG.log(Level.SEVERE, error); if (!ignoreBadSteps) { throw new CucumberModelException(error); } } currentStep = step; } // marks the end of a feature public void eof() { LOG.log(Level.FINE, "eof"); currentFeatureResult = null; currentScenarioResult = null; currentBackground = null; currentStep = null; currentURI = null; } public void syntaxError(String state, String event, List<String> legalEvents, String uri, Integer line) { LOG.log(Level.SEVERE, "syntaxError: - Failed to parse Gherkin json file."); StringBuilder sb = new StringBuilder("Failed to parse Gherkin json file."); sb.append("\tline: ").append(line); sb.append("\turi: ").append(uri); sb.append("\tState: ").append(state); sb.append("\tEvent: ").append(event); throw new CucumberModelException(sb.toString()); } public void done() { // appears to not be called? LOG.log(Level.FINE, "done"); } public void close() { // appears to not be called? LOG.log(Level.FINE, "close"); } // Reporter implementation. // applies to a scenario - any code that is tagged as @Before public void before(Match match, Result result) { if (LOG.isLoggable(Level.FINE)) { LOG.log(Level.FINE, "rep before match: " + match.getLocation()); LOG.log(Level.FINE, "rep result : " + "(passed) " + Result.PASSED.equals(result.getStatus())); LOG.log(Level.FINE, "rep result : " + result.getDuration()); LOG.log(Level.FINE, "rep result : " + result.getErrorMessage()); LOG.log(Level.FINE, "rep result : " + result.getError()); } currentScenarioResult.addBeforeResult(new BeforeAfterResult(match, result)); } // applies to a step, may be in a scenario or a background public void result(Result result) { if (LOG.isLoggable(Level.FINE)) { LOG.log(Level.FINE, "rep result: " + "(passed) " + Result.PASSED.equals(result.getStatus())); LOG.log(Level.FINE, "rep " + result.getDuration()); LOG.log(Level.FINE, "rep " + result.getErrorMessage()); LOG.log(Level.FINE, "rep " + result.getError()); } StepResult stepResult = new StepResult(currentStep, currentMatch, result); if (currentBackground != null) { currentBackground.addStepResult(stepResult); } else { currentScenarioResult.addStepResult(stepResult); } currentStep = null; currentMatch = null; } // applies to a scenario - any code that is tagged as @After public void after(Match match, Result result) { if (LOG.isLoggable(Level.FINE)) { LOG.log(Level.FINE, "rep after match : " + match.getLocation()); LOG.log(Level.FINE, "rep result : " + "(passed) " + Result.PASSED.equals(result.getStatus())); LOG.log(Level.FINE, "rep result : " + result.getDuration()); LOG.log(Level.FINE, "rep result : " + result.getErrorMessage()); LOG.log(Level.FINE, "rep result : " + result.getError()); } currentScenarioResult.addAfterResult(new BeforeAfterResult(match, result)); } // applies to a step public void match(Match match) { // applies to a step. LOG.log(Level.FINE, "rep match: {0}", match.getLocation()); if (currentMatch != null) { LOG.log(Level.SEVERE, "Match: " + match.getLocation() + " received before previous Match: " + currentMatch.getLocation()+ "handled"); throw new CucumberModelException("Match: " + match.getLocation() + " received before previous Match: " + currentMatch.getLocation()+ "handled"); } currentMatch = match; } public void embedding(String mimeType, byte[] data) { LOG.log(Level.FINE, "rep embedding: {0}", mimeType); try { File f = CucumberUtils.createEmbedFile(data); EmbeddedItem embed = new EmbeddedItem(mimeType, f.getName()); currentScenarioResult.addEmbeddedItem(embed); } catch (IOException ex) { throw new CucumberPluginException("Failed to write embedded data to temporary file", ex); } } public void write(String text) { LOG.log(Level.FINE, "rep write: {0}", text); } }