2008년 2월 5일 화요일

Buildbot과 Subversion으로 빌드와 테스트 자동화하기 2 - Lisp 프로그램의 빌드와 테스트

쉘에서 명령어 하나로 빌드와 테스트를 다 할 수 있다면 Buildbot을 이용해서 빌드와 테스트를 자동화하는 것은 매우 쉬울 것이다. 내 경우에는 build-and-test.lisp 파일에 빌드와 테스트 과정을 저장하고 쉘에서 다음과 같은 명령어로 빌드와 테스트를 수행하였다.

> sbcl --load build-and-test.lisp

build-and-test 파일의 내용은 대체로 다음과 같이 구성될 수 있을 것이다.

; 이 파일이 로드될 시점의 디렉토리를 asdf:*central-registry*에 동적으로 추가
(push (make-pathname :directory (pathname-directory *load-truename*))
asdf:*central-registry*)
; 어플리케이션과 테스트를 빌드
(require 'application)
(require 'application-test)
; 테스트를 수행
(application-test:run-all-tests)
; Buildbot이 빌드와 테스트가 성공한 것으로 인식하도록 sbcl을 정상 종료
(quit)

사실 asdf를 이용해서 시스템을 구성하고 컴파일하는 것에 대해서는 설명이 조금 길어질 수 있다. 하지만 언제나 예를 보는 것이 가장 쉽기 때문에 내 예를 보이도록 하겠다. SBCLlisp-unit을 사용한 예제이다.

내 디렉토리 구조가 다음과 같다고 할 때,

paip> find . -not -iname "*.fasl" -and -not -iname "*~"
.
./build-and-test.lisp
./paip-test.asd
./paip.asd
./src
./src/chapter-1.lisp
./src/chapter-2.lisp
./src/paip.lisp
./test
./test/chapter-1.lisp
./test/chapter-2.lisp
./test/paip-test.lisp

각각의 파일 내용은 다음과 같다.

paip.asd

;;;; -*- Mode: Lisp; Syntax: ANSI-Common-Lisp; Base: 10 -*-
(defpackage #:paip-asd
(:use :cl :asdf))

(in-package :paip-asd)

(defsystem paip
:name "paip"
:components ((:module src
:components ((:file "paip")
(:file "chapter-1"
:depends-on ("paip"))
(:file "chapter-2"
:depends-on ("paip"))))))

paip-test.asd

;;;; -*- Mode: Lisp; Syntax: ANSI-Common-Lisp; Base: 10 -*-
(defpackage #:paip-test-asd
(:use :cl :asdf))

(in-package :paip-test-asd)

(defsystem paip-test
:name "paip-test"
:depends-on ("paip" :lisp-unit)
:components ((:module test
:components ((:file "paip-test")
(:file "chapter-1"
:depends-on ("paip-test"))
(:file "chapter-2"
:depends-on ("paip-test"))))))

src/paip.lisp

(defpackage #:paip
(:use :cl)
(:documentation
"Exercises of Paradigms of Artificial Intelligence Programming"))

src/chapter-1.lisp

(in-package :paip)

(export '(power count-atoms))

; 1.2
(defun power (base exp)
(if (= exp 0)
1
(do ((result 1 (* result base))
(exp exp (- exp 1)))
((= exp 0) result))))

; 1.3
(defun count-atoms (expression)
(if (null expression)
0
(if (atom expression)
1
(+ (count-atoms (first expression)) (count-atoms (rest expression))))))

src/chapter-2.lisp

(in-package :paip)

(export '(rule-lhs rule-rhs rewrites))

(defun rule-lhs (rule)
"The left-hand side of a rule."
(first rule))

(defun rule-rhs (rule)
"The right-hand side of a rule."
(rest (rest rule)))

(defun rewrites (category grammar)
"Return a list of the possible rewrites for this category."
(rule-rhs (assoc category grammar)))

test/paip-test.lisp

(defpackage #:paip-test
(:use :cl :paip :lisp-unit)
(:export #:paip-test))

(in-package :paip-test)

(defun paip-test ()
(run-all-tests :paip-test))

test/chapter-1.lisp

(in-package :paip-test)

; 1.2
(define-test power
(assert-equal 1 (power 2 0))
(assert-equal 4 (power 2 2))
(assert-equal 1024 (power 2 10)))

; 1.3
(define-test count-atoms
(assert-equal 0 (count-atoms nil))
(assert-equal 1 (count-atoms 'a))
(assert-equal 3 (count-atoms '(1 2 3)))
(assert-equal 5 (count-atoms '((1) 2 (3 (4) 5)))))

test/chater-2.lisp

(in-package :paip-test)

(define-test rule-lhs
(assert-equal (rule-lhs '(noun -> man ball woman)) 'noun))

(define-test rule-rhs
(assert-equal (rule-rhs '(noun -> man ball woman)) '(man ball woman)))

(defmacro with-simple-grammar (&body body)
`(let ((simple-grammar '((sentence -> (noun-phrase verb-phrase))
(noun-phrase -> (article noun))
(verb-phrase -> (verb noun-phrase))
(article -> the a)
(noun -> man ball woman table)
(verb -> hit took saw liked))))
,@body))

(define-test rewrites
(with-simple-grammar
(assert-equal '(man ball woman table) (rewrites 'noun simple-grammar))
(assert-equal '((article noun)) (rewrites 'noun-phrase simple-grammar))))

build-and-test.lisp

(push (make-pathname :directory (pathname-directory *load-truename*))
asdf:*central-registry*)
(require 'paip-test)
(paip-test:paip-test)
(format t "~%")
(quit)

몇 가지 주의할 점을 얘기해보면,
1. 파일과 디렉토리 사이의 의존 관계를 :depends-on 으로 명확히 표시해 주어야 한다. 그저 의존 순서에 따라 순차적으로 적게되면 때때로 패키지 에러가 발생할 수 있다.

2. 파일이 로드되는 순간에 현재 디렉토리를 알아내서 asdf:*central-registry*에 동적으로 추가할 수 있어야 한다. 그래야 체크아웃하는 디렉토리가 어디인지에 영향을 받지 않고 빌드를 수행할 수 있다. 위의 예대로 하면 되지만, 동적으로 디렉토리와 패스를 알아내는 것에 관심이 있다면 이 포스트를 참조하면 좋다.

댓글 없음: