Skip to content

Commit

Permalink
refactoring for edge cases with jar, classpath etc ref #520
Browse files Browse the repository at this point in the history
  • Loading branch information
ptrthomas committed Sep 9, 2018
1 parent eb7094c commit 01dd50b
Show file tree
Hide file tree
Showing 15 changed files with 170 additions and 102 deletions.
21 changes: 20 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2804,7 +2804,7 @@ So you get the picture, any kind of complicated 'sign-in' flow can be scripted a
Do look at the documentation and example for [`configure headers`](#configure-headers) also as it goes hand-in-hand with `call`. In the above example, the end-result of the `call` to `my-signin.feature` resulted in the `authToken` variable being initialized. Take a look at how the [`configure headers`](#configure-headers) example uses the `authToken` variable.

### Call Tag Selector
You can "select" a single `Scenario` (or `Scenario`-s or `Scenario Outline`-s) by appending a "tag selector" at the end of the feature-file you are calling. For example:
You can "select" a single `Scenario` (or `Scenario`-s or `Scenario Outline`-s or even specific `Examples` rows) by appending a "tag selector" at the end of the feature-file you are calling. For example:

```cucumber
call read('classpath:my-signin.feature@name=someScenarioName')
Expand Down Expand Up @@ -3200,6 +3200,25 @@ has more information on tags. Also see [`first.feature`](karate-demo/src/test/ja

> For advanced users, Karate supports being able to query for tags within a test, and even tags in a `@name=value` form. Refer to [`karate.tags`](#karate-tags) and [`karate.tagValues`](#karate-tagvalues).
### Tags And Examples
A little-known capability of the Cucumber / Gherkin syntax is to be able to tag even specific rows in a bunch of examples ! You have to repeat the `Examples` section for each tag. The example below combines this with the advanced features described above.

```cucumber
Scenario Outline: examples partitioned by tag
* def vals = karate.tagValues
* match vals.region[0] == '<expected>'
@region=US
Examples:
| expected |
| US |
@region=GB
Examples:
| expected |
| GB |
```

## Dynamic Port Numbers
In situations where you start an (embedded) application server as part of the test set-up phase, a typical challenge is that the HTTP port may be determined at run-time. So how can you get this value injected into the Karate configuration ?

Expand Down
114 changes: 68 additions & 46 deletions karate-core/src/main/java/com/intuit/karate/FileUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public static ScriptValue readFile(String text, ScenarioContext context) {
String contents = readFileAsString(text, context);
return new ScriptValue(contents, text);
} else if (isFeatureFile(text)) {
Resource fr = resolvePath(text, context);
Resource fr = toResource(text, context);
Feature feature = FeatureParser.parse(fr);
feature.setCallTag(pair.right);
return new ScriptValue(feature, text);
Expand Down Expand Up @@ -132,16 +132,17 @@ private static StringUtils.Pair parsePathAndTags(String text) {
}
}

public static Feature resolveFeature(String path) {
public static Feature parseFeatureAndCallTag(String path) {
StringUtils.Pair pair = parsePathAndTags(path);
Feature feature = FeatureParser.parse(pair.left);
feature.setCallTag(pair.right);
return feature;
}

private static Resource resolvePath(String path, ScenarioContext context) {
private static Resource toResource(String path, ScenarioContext context) {
if (isClassPath(path) || isFilePath(path)) {
return new Resource(fromRelativeClassPath(path), path);
ClassLoader cl = context.getClass().getClassLoader();
return new Resource(fromRelativeClassPath(path, cl), path);
} else {
try {
Path parentPath = context.featureContext.parentPath;
Expand All @@ -165,22 +166,11 @@ private static String readFileAsString(String path, ScenarioContext context) {
}

public static InputStream getFileStream(String path, ScenarioContext context) {
Resource fr = resolvePath(path, context);
Resource fr = toResource(path, context);
return fr.getStream();
}

private static InputStream getStream(File file) {
try {
return new FileInputStream(file);
} catch (FileNotFoundException e) {
throw new KarateFileNotFoundException(e.getMessage());
}
}

public static String toPackageQualifiedName(String path) {
if (path == null) {
return "(in memory)";
}
path = removePrefix(path);
String packagePath = path.replace("/", "."); // assumed to be already in non-windows form
if (packagePath.endsWith(".feature")) {
Expand Down Expand Up @@ -318,8 +308,14 @@ public static void renameFileIfZeroBytes(String fileName) {
}
}

public static String toRelativeClassPath(File file, ClassLoader cl) {
Path path = file.toPath();
public static boolean isFile(Path path) {
return "file".equals(path.toUri().getScheme());
}

public static String toRelativeClassPath(Path path, ClassLoader cl) {
if (!isFile(path)) {
return CLASSPATH_COLON + path.toString();
}
for (Path rootPath : getAllClassPaths(cl)) {
if (path.startsWith(rootPath)) {
Path relativePath = rootPath.relativize(path);
Expand All @@ -329,29 +325,49 @@ public static String toRelativeClassPath(File file, ClassLoader cl) {
return null;
}

public static File getDirContaining(Class clazz) {
public static Path getDirContaining(Class clazz) {
String relativePath = clazz.getPackage().getName().replace('.', '/');
return fromRelativeClassPath(CLASSPATH_COLON + relativePath);
ClassLoader cl = clazz.getClassLoader();
Path path = getPathIfJar(relativePath, cl);
if (path != null) {
return path;
}
// get file path
try {
URL url = cl.getResource(relativePath);
return Paths.get(url.toURI());
} catch (Exception e) {
throw new RuntimeException(e);
}

}

public static File getFileRelativeTo(Class clazz, String path) {
File dir = getDirContaining(clazz);
return new File(dir.getPath() + File.separator + path);
Path dirPath = getDirContaining(clazz);
return new File(dirPath + File.separator + path);
}

public static String toRelativeClassPath(Class clazz) {
File dir = getDirContaining(clazz);
return toRelativeClassPath(dir, clazz.getClassLoader());
Path dirPath = getDirContaining(clazz);
return toRelativeClassPath(dirPath, clazz.getClassLoader());
}

public static File fromRelativeClassPath(String relativePath) {
public static Path fromRelativeClassPath(String relativePath, ClassLoader cl) {
relativePath = removePrefix(relativePath);
try {
return Paths.get(cl.getResource(relativePath).toURI());
} catch (Exception e) {
throw new RuntimeException(e);
}
}

public static Path fromRelativeClassPath(String relativePath, Path parentPath) {
boolean classpath = isClassPath(relativePath);
relativePath = removePrefix(relativePath);
if (classpath) { // use class-loader resolution
String filePath = Thread.currentThread().getContextClassLoader().getResource(relativePath).getFile();
return new File(filePath);
if (classpath) { // use context file-system resolution
return parentPath.resolve(relativePath);
} else {
return new File(relativePath);
return new File(relativePath).toPath();
}
}

Expand Down Expand Up @@ -395,31 +411,37 @@ private static FileSystem getFileSystem(URI uri) {
}
}

private static Path getPathIfJar(String relativePath, ClassLoader cl) {
try {
URL url = cl.getResource(relativePath);
if (url != null && url.toURI().getScheme().equals("jar")) {
FileSystem fileSystem = getFileSystem(url.toURI());
return fileSystem.getPath(relativePath);
} else {
return null;
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}

public static List<Resource> scanForFeatureFiles(boolean classpath, String searchPath, ClassLoader cl) {
List<Resource> files = new ArrayList();
if (classpath) {
searchPath = removePrefix(searchPath);
if (cl == null) {
cl = Thread.currentThread().getContextClassLoader();
}
try {
URL url = cl.getResource(searchPath);
if (url != null && url.toURI().getScheme().equals("jar")) {
FileSystem fileSystem = getFileSystem(url.toURI());
Path search = fileSystem.getPath(searchPath);
collectFeatureFiles(search, files);
return files; // exit early
Path search = getPathIfJar(searchPath, cl);
if (search != null) {
collectFeatureFilesFromJar(search, files);
} else {
for (Path rootPath : getAllClassPaths(cl)) {
collectFeatureFiles(rootPath, searchPath, files);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
for (Path rootPath : getAllClassPaths(cl)) {
collectFeatureFiles(rootPath, searchPath, files);
}
return files;
} else {
collectFeatureFiles(null, searchPath, files);
return files;
}
return files;
}

private static void collectFeatureFiles(Path rootPath, String searchPath, List<Resource> files) {
Expand Down Expand Up @@ -451,7 +473,7 @@ private static void collectFeatureFiles(Path rootPath, String searchPath, List<R
}
}

private static void collectFeatureFiles(Path searchPath, List<Resource> files) {
private static void collectFeatureFilesFromJar(Path searchPath, List<Resource> files) {
Stream<Path> stream;
try {
stream = Files.walk(searchPath);
Expand Down
24 changes: 17 additions & 7 deletions karate-core/src/main/java/com/intuit/karate/Resource.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@
package com.intuit.karate;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;

Expand All @@ -35,32 +37,40 @@
public class Resource {

private final boolean file;
private final Path path;
private final Path path;
private final String relativePath;

private final String packageQualifiedName;

public Resource(File file, String relativePath) {
this.file = true;
path = file.toPath();
this.relativePath = relativePath;
this(file.toPath(), relativePath);
}

public Resource(Path path, String relativePath) {
this.path = path;
file = !path.toUri().getScheme().equals("jar");
this.relativePath = relativePath;
packageQualifiedName = FileUtils.toPackageQualifiedName(relativePath);
}

public String getRelativePath() {
return relativePath;
}

public String getPackageQualifiedName() {
return packageQualifiedName;
}

public Path getPath() {
return path;
}

public InputStream getStream() {
try {
return Files.newInputStream(path);
if (file) {
return new FileInputStream(path.toFile());
} else {
return Files.newInputStream(path);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
Expand All @@ -77,6 +87,6 @@ public String getAsString() {
@Override
public String toString() {
return relativePath;
}
}

}
6 changes: 3 additions & 3 deletions karate-core/src/main/java/com/intuit/karate/core/Engine.java
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ public static Result executeStep(Step step, Actions actions) {
public static File saveResultJson(String targetDir, FeatureResult result) {
List<Map> single = Collections.singletonList(result.toMap());
String json = JsonUtils.toJson(single);
File file = new File(targetDir + File.separator + result.getFeature().getPackageQualifiedName() + ".json");
File file = new File(targetDir + File.separator + result.getPackageQualifiedName() + ".json");
FileUtils.writeToFile(file, json);
return file;
}
Expand Down Expand Up @@ -213,7 +213,7 @@ public static File saveResultXml(String targetDir, FeatureResult result) {
doc.appendChild(root);
root.setAttribute("name", result.getDisplayUri()); // will be uri
root.setAttribute("skipped", "0");
String baseName = result.getFeature().getPackageQualifiedName();
String baseName = result.getPackageQualifiedName();
int testCount = 0;
int failureCount = 0;
long totalDuration = 0;
Expand Down Expand Up @@ -370,7 +370,7 @@ public static File saveResultHtml(String targetDir, FeatureResult result) {
String js = getFile("report-template.js");
Document doc = XmlUtils.toXmlDoc(html);
XmlUtils.setByPath(doc, "/html/body/img", svg);
String baseName = result.getFeature().getPackageQualifiedName();
String baseName = result.getPackageQualifiedName();
set(doc, "/html/head/title", baseName);
set(doc, "/html/head/script", js);
for (ScenarioResult sr : result.getScenarioResults()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public ExecutionContext(FeatureContext featureContext, CallContext callContext,
if (!logFileDir.exists()) {
logFileDir.mkdirs();
}
String basePath = featureContext.feature.getPackageQualifiedName();
String basePath = featureContext.feature.getResource().getPackageQualifiedName();
appender = new FileLogAppender(logFileDir.getPath() + File.separator + basePath + ".log", featureContext.logger);
} else {
appender = LogAppender.NO_OP;
Expand Down
6 changes: 0 additions & 6 deletions karate-core/src/main/java/com/intuit/karate/core/Feature.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ public class Feature {
public static final String KEYWORD = "Feature";

private final Resource resource;
private final String packageQualifiedName;

private int line;
private List<Tag> tags;
Expand All @@ -55,7 +54,6 @@ public class Feature {

public Feature(Resource resource) {
this.resource = resource;
this.packageQualifiedName = FileUtils.toPackageQualifiedName(resource.getRelativePath());
}

public boolean isBackgroundPresent() {
Expand Down Expand Up @@ -184,10 +182,6 @@ public Path getPath() {
public String getRelativePath() {
return resource.getRelativePath();
}

public String getPackageQualifiedName() {
return packageQualifiedName;
}

public int getLine() {
return line;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ public static Feature parse(Resource resource) {
}

public static Feature parse(String path) {
File file = FileUtils.fromRelativeClassPath(path);
ClassLoader cl = Thread.currentThread().getContextClassLoader();
Path file = FileUtils.fromRelativeClassPath(path, cl);
Resource resource = new Resource(file, path);
return FeatureParser.parse(resource);
}
Expand Down
Loading

0 comments on commit 01dd50b

Please sign in to comment.