Skip to content

Commit

Permalink
Initial work on Clucumber, the CL adapter to Cucumber.
Browse files Browse the repository at this point in the history
  • Loading branch information
antifuchs committed Apr 25, 2010
0 parents commit 16e3171
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 0 deletions.
5 changes: 5 additions & 0 deletions clucumber.asd
@@ -0,0 +1,5 @@
(asdf:defsystem clucumber
:depends-on (:cl-interpol :cl-ppcre :trivial-backtrace :usocket)
:serial t
:components ((:file "packages")
(:file "server")))
23 changes: 23 additions & 0 deletions features/support/load-order.feature
@@ -0,0 +1,23 @@
Feature: Load order of files

As a user of Clucumber, I want the files I specify to be loaded in a
specific order, so that I can rely on definitions in one file to be
present in the other.

Scenario: Support files are loaded first

Given I start clucumber in fixtures/load-order/
When I load the clucumber-specific files
Then support/a.lisp should be loaded before step_definitions/a.lisp

Scenario: Files are loaded in alphabetical order

Given I start clucumber in fixtures/load-order/
When I load the clucumber-specific files

Then support/a.lisp should be loaded before support/b.lisp
And support/b.lisp should be loaded before support/c.lisp

And step_definitions/a.lisp should be loaded before step_definitions/b.lisp
And step_definitions/b.lisp should be loaded before step_definitions/c.lisp

14 changes: 14 additions & 0 deletions features/support/packages.feature
@@ -0,0 +1,14 @@
Feature: Packages

As a user of clucumber, I want to define a package that includes
clucumber functionality, so that I can refer to that package more
conveniently from step definitions.

Scenario: Defining a package in support

When I start clucumber in fixtures/packages/
Then the current package should be default-package

When I define some-other-package as the test package
Then the current package should be some-other-package

14 changes: 14 additions & 0 deletions packages.lisp
@@ -0,0 +1,14 @@
(cl:defpackage #:clucumber-external
(:export #:start))

(cl:defpackage #:clucumber-steps
(:export #:define-test-package #:*test-package*
#:Given #:When #:Then)
(:use #:cl #:cl-interpol))

(cl:defpackage #:clucumber-user
(:export)
(:use #:cl #:clucumber-steps))

(cl:defpackage #:clucumber
(:use #:cl #:clucumber-steps #:clucumber-external))
92 changes: 92 additions & 0 deletions server.lisp
@@ -0,0 +1,92 @@
(cl:in-package #:clucumber)

(defun load-definitions (base-pathname)
(let ((support-files (directory (merge-pathnames (make-pathname :directory '(:relative "support"
:wild-inferiors)
:name :wild
:type "lisp")
base-pathname)))
(step-files (directory (merge-pathnames (make-pathname :directory '(:relative "step_definitions"
:wild-inferiors)
:name :wild
:type "lisp")
base-pathname))))
(dolist (files (list support-files step-files))
(let ((*readtable* (copy-readtable))
(*package* *test-package*))
(cl-interpol:enable-interpol-syntax)
(mapc #'load (sort files #'string>
:key (lambda (path)
(enough-namestring path base-pathname))))))))

(defun serve-cucumber-requests (socket)
(let ((stream (usocket:socket-stream socket)))
(unwind-protect
(loop with eof-value = (gensym)
for line = (read-line stream nil eof-value)
until (eql line eof-value)
do (catch 'exited
(let ((did-not-unwind nil)
(*debugger-hook* (lambda (condition prev-hook)
(declare (ignore prev-hook))
(print :debugger stream)
(prin1 (trivial-backtrace:print-backtrace condition
:output nil))
(throw 'exited nil))))
(unwind-protect
(progn
(call-step line)
(print :ok stream)
(setf did-not-unwind t))
(unless did-not-unwind
(print :unwind stream)
(terpri stream)))))))))

;;; Step definitions


(defparameter *steps* ())

(defun call-step (line)
(let ((matches (remove-if-not (lambda (regexp)
(cl-ppcre:scan regexp line))
*steps* :key #'car)))
(unless (= 1 (length matches))
(error "Ambiguous step definitions matching ~S: ~S" line matches))
(let ((regex (car (first matches)))
(function (cdr (first matches))))
(apply function (coerce (nth-value 1 (cl-ppcre:scan-to-strings regex line)) 'list)))))

(defun add-step (regex function)
(let ((existing-step (find regex *steps* :key #'car :test #'string=)))
(if existing-step
(setf (cdr existing-step) function)
(setf *steps* (nconc *steps*
(list (cons regex function))))))
*steps*)

(defmacro Given (regex args &body body)
`(eval-when (:compile-toplevel :load-toplevel :execute)
(add-step ,regex (lambda (,@args) ,@body))))

(defmacro Then (regex args &body body)
`(eval-when (:compile-toplevel :load-toplevel :execute)
(add-step ,regex (lambda (,@args) ,@body))))

;;; TODO: Given, When, Then.

;;; Packages

(defun clucumber-external:start (base-pathname host port)
(load-definitions base-pathname)
(let ((socket (usocket:socket-connect host port)))
(serve-cucumber-requests socket)))

(defvar clucumber-steps:*test-package* (find-package :clucumber-user))

(defmacro clucumber-steps:define-test-package (name &rest defpackage-arguments)
`(eval-when (:compile-toplevel :load-toplevel :execute)
(defpackage ,name
(:use #:clucumber)
,@defpackage-arguments)
(setf *test-package* (find-package ',name))))

0 comments on commit 16e3171

Please sign in to comment.