Skip to content

Commit

Permalink
Merge branch '1.0.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
nilswloka committed Apr 28, 2012
2 parents 184a2ba + 30a94cb commit 7356635
Show file tree
Hide file tree
Showing 7 changed files with 234 additions and 67 deletions.
3 changes: 3 additions & 0 deletions .travis.yml
@@ -0,0 +1,3 @@
language: clojure
lein: lein2
script: lein2 cucumber
39 changes: 29 additions & 10 deletions README.md
@@ -1,26 +1,45 @@
[![Build Status](https://secure.travis-ci.org/nilswloka/lein-cucumber.png)](http://travis-ci.org/nilswloka/lein-cucumber)

# lein-cucumber

This is a very simple leiningen plugin for use with [cucumber-jvm](https://github.com/cucumber/cucumber-jvm).
This is a leiningen plugin for use with [cucumber-jvm](https://github.com/cucumber/cucumber-jvm).

## Usage

1. Add `[lein-cucumber "0.2.0"]` to `:dev-dependencies` in your project.clj
1. Add `[lein-cucumber "1.0.0]` to `:plugins` in your project.clj
2. Run `lein deps` to fetch all dependencies.
3. Run all Cucumber features with `lein cucumber`
3. Run all Cucumber features with `lein2 cucumber`

## Please note

In the current version, you cannot specify any configuration options. The following settings are hard-coded into the plugin:
lein-cucumber requires Leiningen 2.

## Configuration

Feature paths are resolved in the following order:

1. Command line options (e.g. `lein2 cucumber my-features`) override all other settings.
2. If no command line options for feature paths are given, the `:cucumber-feature-paths` parameter in your project.clj will be used (e.g. `:cucumber-feature-paths ["test/features/"]`).
3. If neither command line options nor a parameter is used, lein-cucumber looks for features in the `features/` directory.

Glue paths are resolved similarily:

* A progress report will be printed to the console.
* The complete report (formatted with `PrettyFormatter`) will be written to `test-reports/cucumber.out`. Consider adding `:extra-files-to-clean ["test-reports"]` to your project.clj.
1. Command line options (e.g. `lein2 cucumber --glue somewhere/my_stepdefs`) override all other settings.
2. If no command line options for glue paths are given, step definitions will be loaded from `step_definitions/` directories inside your feature directories.

## Other settings

The following settings are hard-coded into the plugin:

* A summary report will be printed to the console.
* The complete report (formatted with `CucumberPrettyFormatter`) will be written to `test-reports/cucumber.out` inside your project's target directory (usually `target/`).
* Leiningen will exit with the exit status of the cucumber-jvm [runtime](https://github.com/cucumber/cucumber-jvm/blob/master/core/src/main/java/cucumber/runtime/Runtime.java).
* Put your `.feature` files into `features/` (feature-paths can be configured in project.clj using the `:cucumber-feature-path` parameter, e.g `:cucumber-feature-path ["test/features/"]`.)
* Put your step definitions into `features/step_definitions/`

See https://github.com/cucumber/cucumber-jvm/tree/master/clojure/src/test/resources/cucumber/runtime/clojure for an example specification.
See https://github.com/nilswloka/cucumber-jvm/tree/new-clojure-example/examples/clojure_cukes for an example project.

## Unlicense

Written by Nils Wloka, 2012. For licensing information, see UNLICENSE.
Written by Nils Wloka, 2012. For licensing information, see UNLICENSE. [![endorse](http://api.coderwall.com/nilswloka/endorsecount.png)](http://coderwall.com/nilswloka)
Contributions by [Robert P. Levy](https://github.com/rplevy-draker) and [Michael van Acken](https://github.com/mva).
Please have a look at http://unlicense.org if you plan to contribute.

45 changes: 45 additions & 0 deletions features/cucumber.feature
@@ -0,0 +1,45 @@
Feature: lein-cucumber works

Scenario: No features
Given a lein-cucumber project without special configuration
And no features in the "features" directory
When I run lein-cucumber without command line arguments
Then the output should include "No features found"

Scenario: Features, but no step definitions
Given a lein-cucumber project without special configuration
And a feature in the "features" directory
And no step definitions in the "features/step_definitions" directory
When I run lein-cucumber without command line arguments
Then the output should include "You can implement missing steps with the snippets below:"

Scenario: Features and step definitions
Given a lein-cucumber project without special configuration
And a feature in the "features" directory
And a step definition in the "features/step_definitions" directory
When I run lein-cucumber without command line arguments
Then the step should be executed
And the output should include "."

Scenario: Specify feature directory in project.clj
Given a lein-cucumber project with the following parameters:
| :cucumber-feature-paths |
| ["test/features/"] |
And a feature in the "test/features/" directory
And a step definition in the "test/features/step_definitions" directory
When I run lein-cucumber without command line arguments
Then the step should be executed

Scenario: Specify feature directory via command line
Given a lein-cucumber project without special configuration
And a feature in the "foo" directory
And a step definition in the "foo/step_definitions" directory
When I run lein-cucumber with arguments "foo"
Then the step should be executed

Scenario: Specify glue directory via command line
Given a lein-cucumber project without special configuration
And a feature in the "features" directory
And a step definition in the "foo" directory
When I run lein-cucumber with arguments "--glue foo"
Then the step should be executed
87 changes: 87 additions & 0 deletions features/step_definitions/cucumber_steps.clj
@@ -0,0 +1,87 @@
(use 'clojure.java.io)
(use 'clojure.java.shell)
(use 'leiningen.cucumber)
(require '[leiningen.core.project :as project])
(require '[leiningen.core.user :as user])
(import '[org.apache.commons.io FileUtils])
(import '[java.io StringWriter])

(def project-directory
(file "target" "test_project"))

(def feature-content
"Feature: test-feature\n Scenario: A test\n Given a step\n")

(def step-content
'(Given #"^a step$" [] (println "{{{STEP EXECUTED}}}")))

(def result (atom ""))

(defn- project-configuration
([] '(defproject test-project "0.1.0"
:description "A test project"))
([parameters]
(seq (concat (project-configuration) parameters))))

(defn- create-project [project-file-content]
(let [project-file (file project-directory "project.clj")]
(.mkdirs project-directory)
(FileUtils/cleanDirectory project-directory)
(spit project-file project-file-content)))

(defn- create-empty-directory [path]
(let [empty-directory (file project-directory path)]
(.mkdirs empty-directory)
(FileUtils/cleanDirectory empty-directory)))

(defn- create-file [path name content]
(let [the-file (file project-directory path name)]
(make-parents the-file)
(delete-file the-file true)
(spit the-file content)))

(defn- read-project []
(with-redefs [user/profiles (constantly {})]
(project/read "target/test_project/project.clj")))

(defn- writing-to-result [f]
(let [out-writer (java.io.StringWriter.)]
(with-redefs [*out* out-writer]
(f)
(reset! result (.toString out-writer)))))

(defn- assert-output-includes [text]
(assert (.contains @result text)))

(Given #"^a lein-cucumber project without special configuration$" []
(create-project (project-configuration)))

(Given #"^a lein-cucumber project with the following parameters:$" [parameter-table]
(let [parameters (map (comp read-string first) (.raw parameter-table))]
(create-project (project-configuration parameters))))

(Given #"^no features in the \"([^\"]*)\" directory$" [path]
(create-empty-directory path))

(Given #"^no step definitions in the \"([^\"]*)\" directory$" [path]
(create-empty-directory path))

(Given #"^a feature in the \"([^\"]*)\" directory$" [path]
(create-file path "test.feature" feature-content))

(Given #"^a step definition in the \"([^\"]*)\" directory$" [path]
(create-file path "test_steps.clj" step-content))

(When #"^I run lein-cucumber without command line arguments$" []
(writing-to-result
(fn [] (cucumber (read-project)))))

(When #"^I run lein-cucumber with arguments \"([^\"]*)\"$" [args]
(writing-to-result
(fn [] (apply cucumber (concat (vector (read-project)) (re-seq #"\S+\b" args))))))

(Then #"^the output should include \"([^\"]*)\"$" [expected-output]
(assert-output-includes expected-output))

(Then #"^the step should be executed" []
(assert-output-includes "{{{STEP EXECUTED}}}"))
9 changes: 6 additions & 3 deletions project.clj
@@ -1,7 +1,10 @@
(defproject lein-cucumber "0.2.0"
(defproject lein-cucumber "1.0.0"
:description "Run cucumber-jvm specifications with leiningen"
:dependencies [[info.cukes/cucumber-clojure "1.0.0.RC21"]]
:eval-in-leiningen true
:dependencies [[info.cukes/cucumber-clojure "1.0.4"]
[leiningen-core "2.0.0-preview3"]]
:profiles {:test {:dependencies [[commons-io "2.0"]]}}
:plugins [[lein-cucumber "1.0.0"]]
:eval-in :leiningen
:license {:name "Unlicense"
:url "http://unlicense.org/"
:distribution :repo})
46 changes: 34 additions & 12 deletions src/leiningen/cucumber.clj
@@ -1,19 +1,41 @@
(ns leiningen.cucumber
(:use [leiningen.compile :only [eval-in-project]])
(:require [leiningen.core :as core]))
(:use [clojure.java.io])
(:use [leiningen.core.eval :only [eval-in-project]])
(:require [leiningen.core.project :as project])
(:import [cucumber.runtime RuntimeOptions]))

(defn- configure-feature-paths [runtime-options feature-paths]
(when (.. runtime-options featurePaths (isEmpty))
(.. runtime-options featurePaths (addAll feature-paths))))

(defn- configure-glue-paths [runtime-options glue-paths feature-paths]
(when (.. runtime-options glue (isEmpty))
(if (empty? glue-paths)
(.. runtime-options glue (addAll (into [] (map #(str (file % "step_definitions/")) feature-paths))))
(.. runtime-options glue (addAll glue-paths)))))

(defn create-partial-runtime-options [{:keys [cucumber-feature-paths target-path cucumber-glue-paths] :or {cucumber-feature-paths ["features"]}} args]
(let [runtime-options (RuntimeOptions. (into-array String args))]
(configure-feature-paths runtime-options cucumber-feature-paths)
(configure-glue-paths runtime-options cucumber-glue-paths (.featurePaths runtime-options))
runtime-options))

(defn cucumber
"Runs Cucumber features in test/features with glue in test/features/step_definitions"
[project]
[project & args]
(let [runtime (gensym "runtime")
feature-paths (:cucumber-feature-path project)]
runtime-options (create-partial-runtime-options project args)
glue-paths (vec (.glue runtime-options))
feature-paths (vec (.featurePaths runtime-options))
target-path (:target-path project)
project (project/merge-profiles project [:test])]
(eval-in-project
project
(-> project
(update-in [:dependencies] conj
['lein-cucumber "1.0.0"]
['info.cukes/cucumber-clojure "1.0.4"])
(update-in [:source-paths] (partial apply conj) glue-paths))
`(do
(let [~runtime (leiningen.cucumber.util/run-cucumber! ~feature-paths)]
(println)
~(when-not core/*interactive?*
`(System/exit (.exitStatus ~runtime)))))
nil
nil
'(require 'leiningen.cucumber.util))))
(let [~runtime (leiningen.cucumber.util/run-cucumber! ~feature-paths ~glue-paths ~target-path ~(vec args))]
(leiningen.core.main/exit (.exitStatus ~runtime))))
'(require 'leiningen.cucumber.util 'leiningen.core.main))))
72 changes: 30 additions & 42 deletions src/leiningen/cucumber/util.clj
@@ -1,52 +1,40 @@
(ns leiningen.cucumber.util
(:use [clojure.java.io])
(:import [gherkin.formatter PrettyFormatter])
(:import [cucumber.formatter FormatterFactory MultiFormatter])
(:import [cucumber.formatter CucumberPrettyFormatter ProgressFormatter])
(:import [cucumber.io FileResourceLoader])
(:import [cucumber.cli DefaultRuntimeFactory])
(:import [cucumber.runtime.snippets SummaryPrinter])
(:import [java.io File])
(:import [java.util ArrayList]))
(:import [cucumber.runtime.model CucumberFeature])
(:import [cucumber.runtime RuntimeOptions CucumberException]))

(defn- report-writer []
(let [report-directory (file "test-reports")
report-file (file report-directory "cucumber.out")]
(defn- report-writer [target-path]
(let [report-file (file target-path "test-reports" "cucumber.out")]
(make-parents report-file)
(writer report-file)))

(defn- create-multi-formatter []
(let [classloader (.getContextClassLoader (Thread/currentThread))
formatter-factory (FormatterFactory. classloader)
multi-formatter (MultiFormatter. classloader)
out (report-writer)
formatter (PrettyFormatter. out true true)]
(doto multi-formatter
(.add formatter)
(.add (.createFormatter formatter-factory "progress" System/out)))
multi-formatter))

(defn- create-runtime [stepdef-paths]
(let [runtime-factory (DefaultRuntimeFactory.)
classloader (.getContextClassLoader (Thread/currentThread))
glue-paths (ArrayList. stepdef-paths)
file-resource-loader (FileResourceLoader.)]
(.createRuntime runtime-factory file-resource-loader glue-paths classloader false)))
(defn- create-runtime-options [feature-paths glue-paths target-path args]
(let [runtime-options (RuntimeOptions. (into-array String args))]
(when (.. runtime-options featurePaths (isEmpty))
(.. runtime-options featurePaths (addAll feature-paths)))
(when (.. runtime-options glue (isEmpty))
(.. runtime-options glue (addAll glue-paths)))
(doto (.formatters runtime-options)
(.add (CucumberPrettyFormatter. (report-writer target-path)))
(.add (ProgressFormatter. *out*)))
runtime-options))

(defn- gen-stepdef-paths [feature-paths]
(map (fn [path]
(format "%s%sstep_definitions" path (if (= (last path) \/) "" "/")))
feature-paths))
(defn- create-runtime [runtime-options]
(let [classloader (.getContextClassLoader (Thread/currentThread))
resource-loader (FileResourceLoader.)]
(cucumber.runtime.Runtime. resource-loader classloader runtime-options)))

(defn run-cucumber! [feature-paths]
(let [multi-formatter (create-multi-formatter)
feature-paths (if (some identity feature-paths)
feature-paths ["features"])
runtime (create-runtime (gen-stepdef-paths feature-paths))
formatter (.formatterProxy multi-formatter)
reporter (.reporterProxy multi-formatter)
summary-printer (SummaryPrinter. System/out)]
(.run runtime (ArrayList. feature-paths) (ArrayList.) formatter reporter)
(.done formatter)
(.print summary-printer runtime)
(.close formatter)
(defn run-cucumber! [feature-paths glue-paths target-path args]
(let [runtime-options (create-runtime-options feature-paths glue-paths
target-path args)
runtime (create-runtime runtime-options)]
(println "Running cucumber...")
(println "Looking for features in: " (vec (.featurePaths runtime-options)))
(println "Looking for glue in: " (vec (.glue runtime-options)))
(try
(.run runtime)
(catch CucumberException e
(println (.getMessage e))))
runtime))

0 comments on commit 7356635

Please sign in to comment.