|
| 1 | +/* |
| 2 | + * Copyright 2019 Qameta Software OÜ |
| 3 | + * |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | + * you may not use this file except in compliance with the License. |
| 6 | + * You may obtain a copy of the License at |
| 7 | + * |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | + * |
| 10 | + * Unless required by applicable law or agreed to in writing, software |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | + * See the License for the specific language governing permissions and |
| 14 | + * limitations under the License. |
| 15 | + */ |
| 16 | +package io.qameta.allure.cucumber6jvm; |
| 17 | + |
| 18 | +import io.cucumber.plugin.event.TestCase; |
| 19 | +import gherkin.ast.Feature; |
| 20 | +import io.qameta.allure.model.Label; |
| 21 | +import io.qameta.allure.model.Link; |
| 22 | +import io.qameta.allure.util.ResultsUtils; |
| 23 | +import org.slf4j.Logger; |
| 24 | +import org.slf4j.LoggerFactory; |
| 25 | + |
| 26 | +import java.io.File; |
| 27 | +import java.net.URI; |
| 28 | +import java.util.ArrayList; |
| 29 | +import java.util.Arrays; |
| 30 | +import java.util.Deque; |
| 31 | +import java.util.List; |
| 32 | +import java.util.Objects; |
| 33 | +import java.util.Optional; |
| 34 | +import java.util.regex.Pattern; |
| 35 | +import java.util.stream.Collectors; |
| 36 | +import java.util.stream.Stream; |
| 37 | + |
| 38 | +import static io.qameta.allure.util.ResultsUtils.createFeatureLabel; |
| 39 | +import static io.qameta.allure.util.ResultsUtils.createFrameworkLabel; |
| 40 | +import static io.qameta.allure.util.ResultsUtils.createHostLabel; |
| 41 | +import static io.qameta.allure.util.ResultsUtils.createLabel; |
| 42 | +import static io.qameta.allure.util.ResultsUtils.createLanguageLabel; |
| 43 | +import static io.qameta.allure.util.ResultsUtils.createStoryLabel; |
| 44 | +import static io.qameta.allure.util.ResultsUtils.createSuiteLabel; |
| 45 | +import static io.qameta.allure.util.ResultsUtils.createTestClassLabel; |
| 46 | +import static io.qameta.allure.util.ResultsUtils.createThreadLabel; |
| 47 | + |
| 48 | +/** |
| 49 | + * Scenario labels and links builder. |
| 50 | + */ |
| 51 | +@SuppressWarnings({"CyclomaticComplexity", "PMD.CyclomaticComplexity", "PMD.NcssCount", "MultipleStringLiterals"}) |
| 52 | +class LabelBuilder { |
| 53 | + private static final Logger LOGGER = LoggerFactory.getLogger(LabelBuilder.class); |
| 54 | + private static final String COMPOSITE_TAG_DELIMITER = "="; |
| 55 | + |
| 56 | + private static final String SEVERITY = "@SEVERITY"; |
| 57 | + private static final String ISSUE_LINK = "@ISSUE"; |
| 58 | + private static final String TMS_LINK = "@TMSLINK"; |
| 59 | + private static final String PLAIN_LINK = "@LINK"; |
| 60 | + |
| 61 | + private final List<Label> scenarioLabels = new ArrayList<>(); |
| 62 | + private final List<Link> scenarioLinks = new ArrayList<>(); |
| 63 | + |
| 64 | + LabelBuilder(final Feature feature, final TestCase scenario, final Deque<String> tags) { |
| 65 | + final TagParser tagParser = new TagParser(feature, scenario); |
| 66 | + |
| 67 | + while (tags.peek() != null) { |
| 68 | + final String tag = tags.remove(); |
| 69 | + |
| 70 | + if (tag.contains(COMPOSITE_TAG_DELIMITER)) { |
| 71 | + |
| 72 | + final String[] tagParts = tag.split(COMPOSITE_TAG_DELIMITER, 2); |
| 73 | + if (tagParts.length < 2 || Objects.isNull(tagParts[1]) || tagParts[1].isEmpty()) { |
| 74 | + // skip empty tags, e.g. '@tmsLink=', to avoid formatter errors |
| 75 | + continue; |
| 76 | + } |
| 77 | + |
| 78 | + final String tagKey = tagParts[0].toUpperCase(); |
| 79 | + final String tagValue = tagParts[1]; |
| 80 | + |
| 81 | + // Handle composite named links |
| 82 | + if (tagKey.startsWith(PLAIN_LINK + ".")) { |
| 83 | + tryHandleNamedLink(tag); |
| 84 | + continue; |
| 85 | + } |
| 86 | + |
| 87 | + switch (tagKey) { |
| 88 | + case SEVERITY: |
| 89 | + getScenarioLabels().add(ResultsUtils.createSeverityLabel(tagValue.toLowerCase())); |
| 90 | + break; |
| 91 | + case TMS_LINK: |
| 92 | + getScenarioLinks().add(ResultsUtils.createTmsLink(tagValue)); |
| 93 | + break; |
| 94 | + case ISSUE_LINK: |
| 95 | + getScenarioLinks().add(ResultsUtils.createIssueLink(tagValue)); |
| 96 | + break; |
| 97 | + case PLAIN_LINK: |
| 98 | + getScenarioLinks().add(ResultsUtils.createLink(null, tagValue, tagValue, null)); |
| 99 | + break; |
| 100 | + default: |
| 101 | + LOGGER.warn("Composite tag {} is not supported. adding it as RAW", tagKey); |
| 102 | + getScenarioLabels().add(getTagLabel(tag)); |
| 103 | + break; |
| 104 | + } |
| 105 | + } else if (tagParser.isPureSeverityTag(tag)) { |
| 106 | + getScenarioLabels().add(ResultsUtils.createSeverityLabel(tag.substring(1))); |
| 107 | + } else if (!tagParser.isResultTag(tag)) { |
| 108 | + getScenarioLabels().add(getTagLabel(tag)); |
| 109 | + } |
| 110 | + } |
| 111 | + |
| 112 | + final String featureName = feature.getName(); |
| 113 | + final URI uri = scenario.getUri(); |
| 114 | + |
| 115 | + getScenarioLabels().addAll(Arrays.asList( |
| 116 | + createHostLabel(), |
| 117 | + createThreadLabel(), |
| 118 | + createFeatureLabel(featureName), |
| 119 | + createStoryLabel(scenario.getName()), |
| 120 | + createSuiteLabel(featureName), |
| 121 | + createTestClassLabel(scenario.getName()), |
| 122 | + createFrameworkLabel("cucumber4jvm"), |
| 123 | + createLanguageLabel("java"), |
| 124 | + createLabel("gherkin_uri", uri.toString()) |
| 125 | + )); |
| 126 | + |
| 127 | + featurePackage(uri.toString(), featureName) |
| 128 | + .map(ResultsUtils::createPackageLabel) |
| 129 | + .ifPresent(getScenarioLabels()::add); |
| 130 | + } |
| 131 | + |
| 132 | + public List<Label> getScenarioLabels() { |
| 133 | + return scenarioLabels; |
| 134 | + } |
| 135 | + |
| 136 | + public List<Link> getScenarioLinks() { |
| 137 | + return scenarioLinks; |
| 138 | + } |
| 139 | + |
| 140 | + private Label getTagLabel(final String tag) { |
| 141 | + return ResultsUtils.createTagLabel(tag.substring(1)); |
| 142 | + } |
| 143 | + |
| 144 | + /** |
| 145 | + * Handle composite named links. |
| 146 | + * |
| 147 | + * @param tagString Full tag name and value |
| 148 | + */ |
| 149 | + private void tryHandleNamedLink(final String tagString) { |
| 150 | + final String namedLinkPatternString = PLAIN_LINK + "\\.(\\w+-?)+=(\\w+(-|_)?)+"; |
| 151 | + final Pattern namedLinkPattern = Pattern.compile(namedLinkPatternString, Pattern.CASE_INSENSITIVE); |
| 152 | + |
| 153 | + if (namedLinkPattern.matcher(tagString).matches()) { |
| 154 | + final String type = tagString.split(COMPOSITE_TAG_DELIMITER)[0].split("[.]")[1]; |
| 155 | + final String name = tagString.split(COMPOSITE_TAG_DELIMITER)[1]; |
| 156 | + getScenarioLinks().add(ResultsUtils.createLink(null, name, null, type)); |
| 157 | + } else { |
| 158 | + LOGGER.warn("Composite named tag {} does not match regex {}. Skipping", tagString, |
| 159 | + namedLinkPatternString); |
| 160 | + } |
| 161 | + } |
| 162 | + |
| 163 | + private Optional<String> featurePackage(final String uriString, final String featureName) { |
| 164 | + final Optional<URI> maybeUri = safeUri(uriString); |
| 165 | + if (!maybeUri.isPresent()) { |
| 166 | + return Optional.empty(); |
| 167 | + } |
| 168 | + URI uri = maybeUri.get(); |
| 169 | + |
| 170 | + if (!uri.isOpaque()) { |
| 171 | + final URI work = new File("").toURI(); |
| 172 | + uri = work.relativize(uri); |
| 173 | + } |
| 174 | + final String schemeSpecificPart = uri.normalize().getSchemeSpecificPart(); |
| 175 | + final Stream<String> folders = Stream.of(schemeSpecificPart.replaceAll("\\.", "_").split("/")); |
| 176 | + final Stream<String> name = Stream.of(featureName); |
| 177 | + return Optional.of(Stream.concat(folders, name) |
| 178 | + .filter(Objects::nonNull) |
| 179 | + .filter(s -> !s.isEmpty()) |
| 180 | + .collect(Collectors.joining("."))); |
| 181 | + } |
| 182 | + |
| 183 | + private static Optional<URI> safeUri(final String uri) { |
| 184 | + try { |
| 185 | + return Optional.of(URI.create(uri)); |
| 186 | + } catch (Exception e) { |
| 187 | + LOGGER.debug("could not parse feature uri {}", uri, e); |
| 188 | + } |
| 189 | + return Optional.empty(); |
| 190 | + } |
| 191 | +} |
0 commit comments