March 29, 2013

Flightをインストールしてみた

Macで確認しています。

以下、Node.jsがインストールされていて、npmコマンドが使える前提です。

まず、BOWER、Flight、RequireJSをインストールしてGitの初期化をしました。
$ sudo npm install -g bower
$ mkdir -p xxx/flight-sample # 適当な場所
$ cd xxx/flight-sample

# https://github.com/twitter/flight#installation の内容そのままで作成
$ vim component.json 

$ bower install
$ tree
.
├── component.json
└── components
    ├── es5-shim
    │   ├── CHANGES
    │   ├── CONTRIBUTORS.md
    │   ├── LICENSE
    │   ├── README.md
    │   ├── component.json
    │   ├── es5-sham.js
    │   ├── es5-sham.min.js
    │   ├── es5-shim.js
    │   ├── es5-shim.min.js
    │   ├── package.json
    │   └── tests
    │       ├── helpers
    │       │   ├── h-kill.js
    │       │   ├── h-matchers.js
    │       │   └── h.js
    │       ├── index.html
    │       ├── lib
    │       │   ├── jasmine-html.js
    │       │   ├── jasmine.css
    │       │   ├── jasmine.js
    │       │   ├── jasmine_favicon.png
    │       │   └── json2.js
    │       └── spec
    │           ├── s-array.js
    │           ├── s-date.js
    │           ├── s-function.js
    │           ├── s-object.js
    │           └── s-string.js
    ├── flight
    │   ├── CHANGELOG.md
    │   ├── CONTRIBUTING.md
    │   ├── LICENSE
    │   ├── Makefile
    │   ├── README.md
    │   ├── component.json
    │   ├── lib
    │   │   ├── advice.js
    │   │   ├── component.js
    │   │   ├── compose.js
    │   │   ├── index.js
    │   │   ├── logger.js
    │   │   ├── registry.js
    │   │   ├── standalone
    │   │   │   ├── amd-shim.min.js
    │   │   │   └── build.js
    │   │   └── utils.js
    │   ├── package.json
    │   ├── test
    │   │   ├── assets
    │   │   │   ├── es5-sham.js
    │   │   │   ├── es5-shim.js
    │   │   │   ├── jquery.js
    │   │   │   ├── loadrunner.js
    │   │   │   ├── loadrunner_plugins
    │   │   │   │   └── amd.js
    │   │   │   └── require.js
    │   │   ├── jasmine
    │   │   │   ├── jasmine-bootstrap.css
    │   │   │   ├── jasmine-bootstrap.js
    │   │   │   ├── jasmine.js
    │   │   │   └── lib
    │   │   │       └── bootstrap.css
    │   │   ├── phantom-jasmine
    │   │   │   ├── console-runner.js
    │   │   │   └── run_jasmine_test.coffee
    │   │   ├── run
    │   │   │   ├── jasmine_test.html
    │   │   │   └── unit_test_files.js
    │   │   ├── run_tests.js
    │   │   └── tests
    │   │       ├── advice_spec.js
    │   │       ├── constructor_spec.js
    │   │       ├── events_spec.js
    │   │       ├── fn_spec.js
    │   │       ├── instance_spec.js
    │   │       ├── mixin_spec.js
    │   │       ├── registry_spec.js
    │   │       ├── standalone_spec.js
    │   │       └── utils_spec.js
    │   └── tools
    │       └── debug
    │           └── debug.js
    └── jquery
        ├── component.json
        ├── composer.json
        ├── jquery.js
        └── jquery.min.js

$ mkdir components/requirejs
$ wget http://requirejs.org/docs/release/2.1.5/minified/require.js -O components/requirejs/require.js

$ git init .
Initialized empty Git repository in xxx/flight-sample/.git/
$ git add .
$ git commit -m "initial commit"
次に https://github.com/twitter/flight/tree/gh-pages/demo を参考にサンプルを作成しました。

-- requireMain.js --
* 上記のdemoそのまま。
requirejs.config({
    // baseUrl: ''
});

-- app/components/myComponent.js --
* 後述のindex.htmlでattachToされることにより、this.before、this.afterの順で処理が行われる。
'use strict';

define(
    [
        'components/flight/lib/component'
    ],

    function(defineComponent)  {

        return defineComponent(myComponent);

        function myComponent() {

            this.before('initialize', function() {
                this.$node.html('test');
                console.log('myComponent before.');
            });

            this.after('initialize', function() {
                console.log('myComponent after.');
            });

        }

    }
);

-- index.html --
* urlArgsは、RequireJSのキャッシング対策。試験環境以外では出力しないようにすべき。とのこと。
* #my_componentに前述のmyComponentをattachToしている。
<!DOCTYPE html>
<html>
<head>
</head>
<body>

<div id="my_component"></div>

<script src="components/jquery/jquery.js"></script>
<script src="components/es5-shim/es5-shim.js"></script>
<script src="components/es5-shim/es5-sham.js"></script>
<script data-main="requireMain.js" src="components/requirejs/require.js"></script>

<script>
// http://requirejs.org/docs/api.html#config-urlArgs
// http://stackoverflow.com/questions/8315088/prevent-requirejs-from-caching-required-scripts
requirejs.config({
    urlArgs: 'bust=' + (new Date()).getTime()
});
</script>

<script>
require(
    [
        'app/components/myComponent'
    ],

    function(myComponent) {
        myComponent.attachTo('#my_component');
    }
);
</script>
</body>
</html>

上記のGitのdiffを https://gist.github.com/mp-php/5263863 に置いておきました。

まだわからないことだらけですが、いろいろ探っていきたいなー。

March 26, 2013

FuelPHPでセッションの保存先をRedisにしてみた

Macで確認しています。Redisは既にインストールされている状態とします。

参考: MacにRedisをインストールしてみた
http://madroom-project.blogspot.jp/2013/03/macredis.html

デーモン化したRedisを起動してモニタリングしておきます。
$ redis-server ~/redis.conf
$ redis-cli monitor
OK
FuelPHP1.5.3をDLして、必要なファイルを用意します。

app/classes/controller/redis.php
* 確認用の為、適当すぎる内容ですみませんm(_ _)m
<?php

class Controller_Redis extends Controller
{

    public function action_index()
    {
        Session::set('xxx', 'yyy');
        return Response::forge(View::forge('redis/index'));
    }

}
app/views/redis/index.php
* 同様に、適当すぎる内容ですみませんm(_ _)m
<p>index</p>
app/config/session.php
<?php

return array(
    'driver' => 'redis',
    // 以下はcore/config/session.phpのままです。メモとして書いてあります。
    'redis' => array(
        'cookie_name' => 'fuelrid',
        'database' => 'default',
    ),
);
app/config/db.php
<?php

return array(
    // 以下は全てcore/config/db.phpのままです。メモとして書いてあります。
    'redis' => array(
        'default' => array(
            'hostname' => '127.0.0.1',
            'port' => 6379,
            'timeout' => null,
        )
    ),
);
ブラウザからredis/indexにアクセスして、モニタリングしているコンソールへの出力を確認します。


尚、FuelPHPのRedisクラスのドキュメントは以下ですが、forgeのパラメータに$nameとあります。
http://fuelphp.com/docs/classes/redis.html
$nameは、前述のconfig/db.phpにおける"redis.default"の"default"の箇所の指定になります。

MacにRedisをインストールしてみた

メモです。

公式:
http://redis.io/
ダウンロード、インストール、バージョン確認
$ wget http://redis.googlecode.com/files/redis-2.6.11.tar.gz
$ tar zxvf redis-2.6.11.tar.gz
$ cd redis-2.6.11
$ make
$ sudo make install
$ redis-server -v
Redis server v=2.6.11 sha=00000000:0 malloc=libc bits=64
デーモン化する為の設定ファイルを作成
$ vim ~/redis.conf
~/redis.confの内容
daemonize yes
作成した設定ファイルでRedisを起動(Ctrl + C でmonitorを終了)
$ redis-server ~/redis.conf
$ redis-cli monitor
OK
Redisを停止して確認(monitorで確認しているのがみっともないけど。。。)
$ redis-cli shutdown
$ redis-cli monitor
Could not connect to Redis at 127.0.0.1:6379: Connection refused

March 25, 2013

[error] an unknown filter was not added: PHPが出ていた

Apacheのerror.logに以下のエラーが出ていました。
[error] an unknown filter was not added: PHP
あまり情報も無かったんですが、とりあえず原因の箇所をコメントアウト。。。
$ diff /etc/apache2/mods-available/php5filter.conf.20130325 /etc/apache2/mods-available/php5filter.conf
3,4c3,4
<       SetInputFilter PHP
<       SetOutputFilter PHP
---
> #     SetInputFilter PHP
> #     SetOutputFilter PHP
参考:
https://bugs.php.net/bug.php?id=22881

March 24, 2013

FuelPHPのDBドキュメントジェネレータをパッケージとして切り離した

ソースは以下になります。
https://github.com/mp-php/fuel-packages-dbdocs


以前、以下の記事を書きました。

FuelPHPでMySQL/PostgreSQL/SQLiteのドキュメントジェネレータを作ってみました。
http://madroom-project.blogspot.jp/2013/02/fuelphpmysqlpostgresqlsqlite.html

FuelPHPで作ったDBドキュメントジェネレータを改良したのでGitLabのDBドキュメントを生成して公開してみた
http://madroom-project.blogspot.jp/2013/03/fuelphpdbgitlabdb.html


試しにフォーラムで紹介してみると、WanWizard氏から、FuelPHPのモデルを解析するアイデアを頂きました。
http://fuelphp.com/forums/discussion/12005/database-documentation-generator


* MySQL以外のドキュメントも生成したい
* Jenkinsで自動実行したい
* Travis CIでユニットテストをしたい
という点で、FuelPHP一式のプロジェクトとしていましたが、FuelPHPをひいきした機能も欲しいなーと思っていたところだったので、やってみました。

カラム名やコメント内容から、簡単なリレーション判定はしていましたが、パッケージとして切り離したことで、各FuelPHPプロジェクトにインストールが可能になりました。同時に、各FuelPHPプロジェクトのModel設定をリレーション判定に用いることが可能になりました。

もともと、生成に必要な機能はパッケージとして隔離していたので、作業は大したことなかったです。

FuelPHPのパッケージプロジェクトでTravis CIによるユニットテスト実行は、以下を参考にさせて頂きました。

OUIを検索するFuelPHP用のパッケージを作ったよ
http://www.sharkpp.net/blog/2013/03/11/release-ouisearch-fuelphp.html


P.S.
JointJS http://www.jointjs.com/ による簡易ER図生成も試みましたが、案の定、配置の計算が厄介なので、たぶんやらない。。。

March 20, 2013

FuelPHPの物理削除と論理削除の実行結果メモ

FuelPHP1.5.3にて、以下の組み合わせに対して、それぞれ親Modelからdeleteメソッドを実行してみました。その結果をメモしておきます。尚、実行環境はMAMP(Mac)のPHP5.4.4、ドライバはPDOです。

(1) 親: 物理削除モデル / 子: 物理削除モデル
(2) 親: 論理削除モデル / 子: 論理削除モデル
(3) 親: 物理削除モデル / 子: 論理削除モデル
(4) 親: 論理削除モデル / 子: 物理削除モデル

初期化用SQLは以下です。各組み合わせの実行前に、このSQLで初期化しました。
-- -----------------------------------------------------
-- Table `parents`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `parents` ;

CREATE  TABLE IF NOT EXISTS `parents` (
  `id` INT NOT NULL AUTO_INCREMENT ,
  `name` VARCHAR(255) NOT NULL ,
  `created_at` INT NOT NULL ,
  `updated_at` INT NOT NULL ,
  `deleted_at` INT ,
  PRIMARY KEY (`id`) )
ENGINE = InnoDB;

INSERT INTO `parents` (`id`, `name`, `created_at`, `updated_at`) VALUES
(1, 'parent1', 1363709961, 1363709961),
(2, 'parent2', 1363709961, 1363709961);

-- -----------------------------------------------------
-- Table `children`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `children` ;

CREATE  TABLE IF NOT EXISTS `children` (
  `id` INT NOT NULL AUTO_INCREMENT ,
  `parent_id` INT NOT NULL ,
  `name` VARCHAR(255) NOT NULL ,
  `created_at` INT NOT NULL ,
  `updated_at` INT NOT NULL ,
  `deleted_at` INT ,
  PRIMARY KEY (`id`) ,
  INDEX `fk_children_parents_idx` (`parent_id` ASC) )
ENGINE = InnoDB;

INSERT INTO `children` (`id`, `parent_id`, `name`, `created_at`, `updated_at`) VALUES
(1, 1, 'child1', 1363709961, 1363709961),
(2, 1, 'child2', 1363709961, 1363709961),
(3, 2, 'child3', 1363709961, 1363709961);
初期化用SQLに対する検索結果は以下です。
mysql> select * from parents;
+----+---------+------------+------------+------------+
| id | name    | created_at | updated_at | deleted_at |
+----+---------+------------+------------+------------+
|  1 | parent1 | 1363709961 | 1363709961 |       NULL |
|  2 | parent2 | 1363709961 | 1363709961 |       NULL |
+----+---------+------------+------------+------------+
2 rows in set (0.00 sec)

mysql> select * from children;
+----+-----------+--------+------------+------------+------------+
| id | parent_id | name   | created_at | updated_at | deleted_at |
+----+-----------+--------+------------+------------+------------+
|  1 |         1 | child1 | 1363709961 | 1363709961 |       NULL |
|  2 |         1 | child2 | 1363709961 | 1363709961 |       NULL |
|  3 |         2 | child3 | 1363709961 | 1363709961 |       NULL |
+----+-----------+--------+------------+------------+------------+
3 rows in set (0.00 sec)

使用したModelは、それぞれ以下です。
尚、物理削除モデルと論理削除モデルとで異なるのは、継承するクラスとprotected static $_soft_deleteの有無のみです。

親: 物理削除モデル
<?php

class Model_Parent extends \Orm\Model
{
    protected static $_properties = array(
        'id',
        'name',
        'deleted_at',
        'created_at',
        'updated_at',
    );

    protected static $_observers = array(
        'Orm\Observer_CreatedAt' => array(
            'events' => array('before_insert'),
            'mysql_timestamp' => false,
        ),
        'Orm\Observer_UpdatedAt' => array(
            'events' => array('before_save'),
            'mysql_timestamp' => false,
        ),
    );

    protected static $_has_many = array(
        'comments' => array(
            'key_from' => 'id',
            'model_to' => 'Model_Child',
            'key_to' => 'parent_id',
            'cascade_save' => true,
            'cascade_delete' => true,
        )
    );
}
子: 物理削除モデル
<?php

class Model_Child extends \Orm\Model
{
    protected static $_properties = array(
        'id',
        'parent_id',
        'name',
        'deleted_at',
        'created_at',
        'updated_at',
    );

    protected static $_observers = array(
        'Orm\Observer_CreatedAt' => array(
            'events' => array('before_insert'),
            'mysql_timestamp' => false,
        ),
        'Orm\Observer_UpdatedAt' => array(
            'events' => array('before_save'),
            'mysql_timestamp' => false,
        ),
    );

    protected static $_belongs_to = array(
        'post' => array(
            'key_from' => 'parent_id',
            'model_to' => 'Model_Parent',
            'key_to' => 'id',
            'cascade_save' => false,
            'cascade_delete' => false,
        )
    );
}
親: 論理削除モデル
<?php

class Model_Parent extends \Orm\Model_Soft
{
    protected static $_soft_delete = array(
        'mysql_timestamp' => false,
    );

    protected static $_properties = array(
        'id',
        'name',
        'deleted_at',
        'created_at',
        'updated_at',
    );

    protected static $_observers = array(
        'Orm\Observer_CreatedAt' => array(
            'events' => array('before_insert'),
            'mysql_timestamp' => false,
        ),
        'Orm\Observer_UpdatedAt' => array(
            'events' => array('before_save'),
            'mysql_timestamp' => false,
        ),
    );

    protected static $_has_many = array(
        'comments' => array(
            'key_from' => 'id',
            'model_to' => 'Model_Child',
            'key_to' => 'parent_id',
            'cascade_save' => true,
            'cascade_delete' => true,
        )
    );
}
子: 論理削除モデル
<?php

class Model_Child extends \Orm\Model_Soft
{
    protected static $_soft_delete = array(
        'mysql_timestamp' => false,
    );

    protected static $_properties = array(
        'id',
        'parent_id',
        'name',
        'deleted_at',
        'created_at',
        'updated_at',
    );

    protected static $_observers = array(
        'Orm\Observer_CreatedAt' => array(
            'events' => array('before_insert'),
            'mysql_timestamp' => false,
        ),
        'Orm\Observer_UpdatedAt' => array(
            'events' => array('before_save'),
            'mysql_timestamp' => false,
        ),
    );

    protected static $_belongs_to = array(
        'post' => array(
            'key_from' => 'parent_id',
            'model_to' => 'Model_Parent',
            'key_to' => 'id',
            'cascade_save' => false,
            'cascade_delete' => false,
        )
    );
}

実行に使用したタスクは以下です。
<?php

namespace Fuel\Tasks;

class Delete
{
    public static function run()
    {
        $parent = \Model_Parent::find(1);
        $parent->delete();
    }
}

上記タスク実行後の検索結果は、それぞれ以下です。

(1) 親: 物理削除モデル / 子: 物理削除モデル
mysql> select * from parents;
+----+---------+------------+------------+------------+
| id | name    | created_at | updated_at | deleted_at |
+----+---------+------------+------------+------------+
|  2 | parent2 | 1363709961 | 1363709961 |       NULL |
+----+---------+------------+------------+------------+
1 row in set (0.00 sec)

mysql> select * from children;
+----+-----------+--------+------------+------------+------------+
| id | parent_id | name   | created_at | updated_at | deleted_at |
+----+-----------+--------+------------+------------+------------+
|  3 |         2 | child3 | 1363709961 | 1363709961 |       NULL |
+----+-----------+--------+------------+------------+------------+
1 row in set (0.00 sec)
(2) 親: 論理削除モデル / 子: 論理削除モデル
mysql> select * from parents;
+----+---------+------------+------------+------------+
| id | name    | created_at | updated_at | deleted_at |
+----+---------+------------+------------+------------+
|  1 | parent1 | 1363709961 | 1363712747 | 1363712747 |
|  2 | parent2 | 1363709961 | 1363709961 |       NULL |
+----+---------+------------+------------+------------+
2 rows in set (0.00 sec)

mysql> select * from children;
+----+-----------+--------+------------+------------+------------+
| id | parent_id | name   | created_at | updated_at | deleted_at |
+----+-----------+--------+------------+------------+------------+
|  1 |         1 | child1 | 1363709961 | 1363712747 | 1363712747 |
|  2 |         1 | child2 | 1363709961 | 1363712747 | 1363712747 |
|  3 |         2 | child3 | 1363709961 | 1363709961 |       NULL |
+----+-----------+--------+------------+------------+------------+
3 rows in set (0.00 sec)
(3) 親: 物理削除モデル / 子: 論理削除モデル
mysql> select * from parents;
+----+---------+------------+------------+------------+
| id | name    | created_at | updated_at | deleted_at |
+----+---------+------------+------------+------------+
|  2 | parent2 | 1363709961 | 1363709961 |       NULL |
+----+---------+------------+------------+------------+
1 row in set (0.00 sec)

mysql> select * from children;
+----+-----------+--------+------------+------------+------------+
| id | parent_id | name   | created_at | updated_at | deleted_at |
+----+-----------+--------+------------+------------+------------+
|  1 |         0 | child1 | 1363709961 | 1363712797 | 1363712797 |
|  2 |         0 | child2 | 1363709961 | 1363712797 | 1363712797 |
|  3 |         2 | child3 | 1363709961 | 1363709961 |       NULL |
+----+-----------+--------+------------+------------+------------+
3 rows in set (0.00 sec)
(4) 親: 論理削除モデル / 子: 物理削除モデル
* この組み合わせのみ、エラーが出ました。
Error - Both sides of the relation must be subclasses of Model_Soft if cascade delete is true in PKGPATH/orm/classes/model/soft.php on line 177

March 17, 2013

JSCoverでQUnitのユニットテストのカバー率を取得してみる

試しに、以前作ったChrome ExtensionのSubotageで少しテストケースを書いて、カバー率を取得してみました。(Macで確認しています。)
https://github.com/mp-php/subotage

(1)
JSCoverをダウンロードします。JSCoverは、要Javaです。
http://tntim96.github.com/JSCover/
尚、ライセンスは"GNU General Public License, version 2"と記載されています。

(2)
ダウンロードしたら解凍して、以下のコマンドを実行してJSCoverを起動します。
sudo java -Dfile.encoding=UTF-8 -jar target/dist/JSCover-all.jar -ws --branch --port=81 --document-root=/xxx/subotage_root/assets --report-dir=/yyy/zzz
* "-Dfile.encoding=UTF-8"は、"org.mozilla.javascript.EvaluatorException"が出たので付けました。
* --document-rootは、ユニットテストを実行するhtmlが有る場所です。(htmlは直下に無くても良いです。)
* --report-dirは、レポートの出力ディレクトリです。
* --portは、指定しないと8080がデフォルトになるようです。
* その他は、とりあえずドキュメント通りです。

起動は、以下から確認できます。(ポートは必要に応じて変えて下さい。)
http://localhost:81/jscoverage.html

上記のURLに、ユニットテストを実行するhtmlをパラメータとして付与します。前述の--document-rootからの相対パスになります。
http://localhost:81/jscoverage.html?qunit/test/app/index.html
ユニットテストが実行されました。

Summaryタブからカバレッジが確認できます。



PhantomJSでも、以下の方法で出力出来ました。

(A)
run-jscover-qunit.jsをダウンロードします。
https://github.com/tntim96/JSCover/blob/master/src/test/javascript/lib/PhantomJS/run-jscover-qunit.js

(B)
以下のように実行します。尚、JSCoverが起動している必要があります。(前述の(2)です。)
phantomjs run-jscover-qunit.js http://localhost:81/qunit/test/app/index.html

(C)
JSCoverの起動時に指定した"--report-dir"の中に"phantom"ディレクトリが作成されました。
その中にあるjscoverage.htmlにhttpでアクセスすると、カバレッジが表示されました。

読み込んだjsファイルしか対象になりませんし、その精度はよく分からないですが、何もないよりは良いんじゃないでしょうか。


P.S.
Jenkins等で自動実行する場合は
の「5.5Ant」が参考になりそうです。"Example starting the server"と"Example stopping the server"の間にphantomjsコマンドを挟めば良い気がしますが、今度、実際に確認してみます。("3.5 Report Conversion & Merging"も組み合わせてみよう。)

March 15, 2013

QUnit + PhantomJSをTravis CIで実行してみた

QUnitをフォークしてデフォルトのテストケースをTravis CIで実行してみました。
https://github.com/jquery/qunit

実行に使用した.travis.ymlは以下になります。

--.travis.yml--
branches:
  only:
    - 'master'

script:
  - phantomjs addons/phantomjs/runner.js test/index.html
結果:
https://travis-ci.org/mp-php/qunit/builds/5501504

(runner.js指定するの忘れてコケまくった。。。)

ブラウザからもコマンドからも簡単に実行できるので、暫くはQUnitでいこうかなぁ。(二転三転。。。)

関連:
QUnitでJavaScriptのユニットテストをブラウザからしてみる
http://madroom-project.blogspot.jp/2013/03/qunitjavascript.html

QUnit + PhantomJSでコマンドからユニットテストを実行してみた
http://madroom-project.blogspot.jp/2013/03/qunit-phantomjs.html

QUnit + JUnit reporter for QUnitでxmlレポートを出力してみた
http://madroom-project.blogspot.jp/2013/03/qunit-junit-reporter-for-qunitxml.html

March 14, 2013

QUnit + JUnit reporter for QUnitでxmlレポートを出力してみた

以下の続きです。

QUnitでJavaScriptのユニットテストをブラウザからしてみる
http://madroom-project.blogspot.jp/2013/03/qunitjavascript.html

QUnit + PhantomJSでコマンドからユニットテストを実行してみた
http://madroom-project.blogspot.jp/2013/03/qunit-phantomjs.html

--

今回は、JUnit reporter for QUnitを用いて、XML形式のレポートを出力してみました。

JUnit reporter for QUnit
https://github.com/jquery/qunit-reporter-junit

ダウンロードして解凍、QUnitのaddons/qunit-reporter-junitとして配置してみます。
test/index.htmlでqunit.jsを読み込んでいる直下に、以下を追記します。
<script src="../addons/qunit-reporter-junit/qunit-reporter-junit.js"></script>
<script>
    QUnit.jUnitReport = function(report) {
        console.log(report.xml);
    };
</script>
試しにブラウザからアクセスしてみるとスクリプトエラーが出たので、とりあえず、qunit-reporter-junit.jsに以下の修正を行いました。(適切な修正内容かはわかりません。。。)
76c76
<         if (!data.result) {
---
>         if (!data.result && currentTest != null) {
84a85,88
>         if (currentTest == null) {
>             return;
>         }
>
93a98,101
>         if (currentModule == null) {
>             return;
>         }
>
再度ブラウザからアクセスしてみると、スクリプトエラーが消えて、コンソールにxmlが出力されました。
phantomjsコマンドから実行しても、xmlが出力されました。(最終行の"Took ..."は力技でカットするしか無いのだろうか。)

これでJenkinsでCI出来るかなー。(Travis CIでの実行も確認しなくては。)

関連:
Add-ons | QUnit
http://qunitjs.com/addons/

QUnit + PhantomJSでコマンドからユニットテストを実行してみた

以前、QUnitをブラウザから実行してみました。(単に実行しただけですが。。。)

QUnitでJavaScriptのユニットテストをブラウザからしてみる
http://madroom-project.blogspot.jp/2013/03/qunitjavascript.html

今回は、PhantomJSを用いてコマンドから実行してみました。QUnitには、PhantomJS用のアドオンが最初から入っています。

PhantomJS
http://phantomjs.org/download.html
ダウンロードして解凍、パスを通してバージョンを確認してみます。
phantomjs -v
1.8.2
phantomjsコマンドにrunner.jsを指定して実行してみます。(addons/phantomjsディレクトリに有ります。)
phantomjs runner.js file://localhost/Users/xxx/Desktop/qunit-master/test/index.html
Took 1969ms to run 315 tests. 315 passed, 0 failed.
ドキュメントのExampleはhttpで指定していますが、上記のようにfileで指定しても動きました。
https://github.com/jquery/qunit/tree/master/addons/phantomjs

次は、JUnit Reporterを使ってみよう。

関連:

Add-ons | QUnit
http://qunitjs.com/addons/

March 11, 2013

March 9, 2013

FuelPHPで作ったDBドキュメントジェネレータを改良したのでGitLabのDBドキュメントを生成して公開してみた

2013/03/11 00:26 追記:
ホットキー対応しました。サンプル右上のHotkeysをクリックしてみて下さい。
(暫く使ってみて使いづらかったら割り当て変えます。。。)

2013/03/09 21:12 追記:
検索機能もつけてみました。サンプルも更新してあります。

--

ちょっと前に、fuel-dbdocsというDBドキュメントジェネレータを作りました。
http://madroom-project.blogspot.jp/2013/02/fuelphpmysqlpostgresqlsqlite.html

GitHub:
https://github.com/mp-php/fuel-dbdocs


GitHubやGitLabのように、特定の行(カラム)に対してリンクができて、色付けもされれば便利かなと思い、改良してみました。


説明するよりも見た方が早いと思いますので、試しにGitLabのDBドキュメントを生成して、以下に置いておきました。
http://fueldbdocssample.madroom.net/index.html


例えば、usersテーブルは以下になります。
http://fueldbdocssample.madroom.net/table_users.html

usersテーブルのemailカラムは以下になります。
http://fueldbdocssample.madroom.net/table_users.html#_column_email
* emailカラムと、"Indexes"の"index_users_on_email"に色が付いています。


カラム名から、外部キーの判別も、ある程度はしています。

例えば、users_projectsテーブルだと
http://fueldbdocssample.madroom.net/table_users_projects.html
* user_id
* project_id
が外部キーと判別されています。

この判別処理はfunctionとして設定ファイルに書いてあるので、独自に修正可能です。


あと、設定でWEBフォントの指定もできるので、指定した状態で出力してみました。

よかったら使ってみて下さい。

March 4, 2013

javascriptのreplaceのgフラグとmフラグのメモ

メモです。。。

replace(/xxx/, "yyy")
xxx
xxx
xxx

yyy
xxx
xxx

---

replace(/xxx/g, "yyy")
xxx
xxx
xxx

yyy
yyy
yyy

---

replace(/xxx/m, "yyy")
xxx
xxx
xxx

yyy
xxx
xxx

---

replace(/^xxx/g, "yyy")
xxx
xxx
xxx

yyy
xxx
xxx

---

replace(/^xxx/gm, "yyy")
xxx
xxx
xxx

yyy
yyy
yyy

GitLabとGitHubのhook通知をGoogle Chrome Extensionで受信する仕組みを作ってみた

GitLabとGitHubのhook通知を受信して送信する「Pubotage
https://github.com/mp-php/pubotage
* Node.js
* Socket.IO

Pubotageの通知を受信する「Subotage」(Chrome Extension)
https://github.com/mp-php/subotage
* Socket.IO
* Backbone.js
* Local Storage

を作ってみました。所謂「社内ツール」の想定です。とりあえずはGitLabだけに対応する予定でしたが、その他のサービスとも連携が取れるような作りにしておきたく、ソース構成の確認目的で、GitHubも一緒に対応させました。


以下、Pubotageのインストールと起動方法です。サーバ(非公開サーバを想定)にインストールします。Node.jsはインストール済の想定です。

(1-1) インストール
* wgetとかでも構いません。
$ git clone git://github.com/mp-php/pubotage.git pubotage
$ cd pubotage
$ sudo npm install # 必要なモジュールのインストール
$ cp config.js.default config.js
config.jsでportの設定をして下さい。デフォルトは"1337"になっています。


(1-2) 起動
* foreverを推奨します。
$ sudo npm install -g forever # グローバルインストール
$ forever start pubotage.js # 起動
* nodeコマンドの場合は以下で起動します。(停止はCtrl + C)
$ node pubotage.js

(1-3) 確認
http://example.com:port/
の形式でアクセスして、起動を確認して下さい。(ファイアウォールの設定に注意して下さい。)

(1-4) hookの設定
GitLabあるいはGitHubのhookで指定するURLの形式は、それぞれ
* http://example.com:port/gitlab
* http://example.com:port/github
になります。


以下、Subotageのインストール方法です。

(2-1) インストール
https://github.com/mp-php/subotage を適当な場所にgit cloneあるいはダウンロード。
ツール > 拡張機能 > パッケージ化されていない拡張機能を読み込む...
と進んで、Subotageのディレクトリを選択します。

(2-2) 設定
オプションから、Subotageの設定を行います。
Pubotage Urlの形式は
* http://example.com:port/
になります。

(2-3) 確認
GitLabあるいはGitHubにpushします。Subotageのアイコンが変わることを確認します。受信できない場合、Subotageをリロードしてみて下さい。


Subotageで受信することを「サボる」と呼びたいです。


以下、TODOです。。。
* アイコン
* ユニットテスト

アイコンはまあ、言わずもがなですね。サボテンのアイコンにしたいです。(受信した時に花が咲く感じ。)ユニットテストは、ホントは込み込みで公開したかったのですが、「Chrome Extension + Backbone.js + Local Storage + JSのユニットテストに慣れていない自分」の組み合わせから、今日の朝までには終わりそうにもないし、後回しにしました。(飽きないうちに作ってしまいたかった。。。)

全体的な感想としては、Node.js + Socket.IO + Backbone.js( + Chrome Extension + Local Storage)という初物尽くしで、面白かったです。(ちょこっと調べたことはありました。)


以下、おまけ。

Beastie Boys - Sabotage
(Subotageの由来は「Subscriber + Sabotage」で、そこからPubotageという名前も決まりましたw)


March 3, 2013

Mochaのテストをコマンドラインとブラウザから実行してみる


コマンドからの実行環境は、以下を御覧ください。

MacにMochaをインストールしてみた:
http://madroom-project.blogspot.jp/2013/03/macmocha.html


同じテストをコマンドとブラウザの両方から実行してみました。


(1) MochaをDL、"mocha"というディレクトリ名で解凍
https://github.com/visionmedia/mocha

(2) Mochaのディレクトリに並べる形で"tests"ディレクトリを作成

(3) tests/test_sample.jsを作成
describe('Test Name', function() {
    it('1 + 1 = 2', function() {
        var expected = 2;
        var actual = 1 + 1;
        assert.equal(expected, actual);
    });
});
(4) tests/index.htmlを作成、アクセス
<html>
<head>
<meta charset="utf-8">
<title>Mocha Tests</title>
<link rel="stylesheet" href="../mocha/mocha.css" />
</head>

<body>
<div id="mocha"></div>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script src="../mocha/mocha.js"></script>
<script>mocha.setup('bdd')</script>
<script src="test_sample.js"></script>
<script>
    var assert = function(expr, msg) {
        if (!expr) throw new Error(msg || 'failed');
    };

    assert.equal = function(a, b, msg) {
        if (a != b) throw new Error(msg || ('failed : '+a+','+b));
    };

    $(function() {
        mocha.run();
    });
</script>
</body>
</html>

(5) tests/test.jsを作成
assert = require('assert');
require('./test_sample');
(6) コマンドから実行
$ mocha --reporter spec tests/


  Test Name
    ✓ 1 + 1 = 2 


  1 test complete (2 ms)

MacにMochaをインストールしてみた

Node.jsがインストールされている前提です。


Mochaのインストール
$ sudo npm install -g mocha
sample.jsを作成
var assert = require('assert');

describe('Test Name', function() {
    it('1 + 1 = 2', function() {
        var expected = 2;
        var actual = 1 + 1;
        assert.equal(expected, actual);
    });
});
実行
$ mocha --reporter spec sample.js 


  Test Name
    ✓ 1 + 1 = 2 


  1 test complete (2 ms)
さっきQUnitも試してみたけど、とりあえずMochaでいこうかなぁ。

QUnitでJavaScriptのユニットテストをブラウザからしてみる

公式:
http://qunitjs.com/

GitHub:
https://github.com/jquery/qunit

以下、サンプル実行までの手順です。


(1)
DLして適当に展開。

(2)
index.htmlの
<script src="test.js"></script>
<script src="deepEqual.js"></script>
<script src="swarminject.js"></script>

<script src="app/test.js"></script>
に変更。

(3)
app/test.jsを作成。
test("Test Name", function(){
    ok(true, "ok - success");
    equal(1, "1", "equal - success");
    deepEqual(1, 1, "deepEqual - success");

    ok(false, "ok - fail");
    equal(1, "2", "equal - fail");
    deepEqual(1, "1", "deepEqual - fail");
});

(4)
index.htmlにアクセス。
* 確認の為、わざと3つ失敗させています。

March 2, 2013

Chrome Extensionのメッセージを国際化してみた

以下、関連する公式ドキュメントです。
http://developer.chrome.com/extensions/i18n.html

HTML上に記述するメッセージを国際化するサンプルが見当たらなかったので、以下の方法でやってみました。

_locales/xx/messages.jsonは、以下のようになります。
{
    "page1_message1": {
        "message": "page1_message1_string"
    },
    "page1_message2": {
        "message": "page1_message2_string"
    },
    "page2_message1": {
        "message": "page2_message1_string"
    },
    "page2_message2": {
        "message": "page2_message2_string"
    }
}
専用のjsファイルを用意します。(locale.jsとしました。)
$(document).ready(function() {
    $(".locale").each(function() {
        classes = $(this).attr("class").split(" ");
        $("." + classes[1]).text(chrome.i18n.getMessage(classes[1]));
    });
});
このjsをHTML側で読み込んで、"page1_message1"を当てはめるタグであれば、以下のようにします。
<span class="locale page1_message1"></span>
実際にHTMLを表示して、言語設定毎に正しいメッセージが反映されていればOKです。

Chromeの言語設定については、以下が参考になります。
http://support.google.com/chrome/bin/answer.py?hl=ja&answer=95416

Backbone.localStorageで単一のレコードを保持するmodel

Backbone.localStorageを使うとlocalStorageを保存先としたmodelが簡単に作れます。
https://github.com/jeromegn/Backbone.localStorage
var OptionModel = Backbone.Model.extend({
    localStorage: new Backbone.LocalStorage("options")
});
このmodelを以下のように呼び出して、適当なデータをsetしてsaveしたとします。
var optionModel = new OptionModel();
localStorageには
* options
* options-xxx(xxxはランダム)
という二つのkey/valueが保存されました。optionsにはoptions-xxxのxxxの部分が保存されました。

modelのsetとsaveを再度実行すると
* options
* options-xxx
* options-yyy
になりました。

optionsが、各データのidに該当する情報を管理しているようです。

単一のレコードのみを扱いたい場合は、以下でできました。
var optionModel = new OptionModel({id: 1}); 
key/valueは
* options
* options-1
になりました。

backbone.jsのcollectionで古いmodelを削除する

localStorageに対するcollectionのmodelで最大個数を制御したかったので、以下の方法でやってみました。view内のサンプルになります。(21個目以上が削除されます。)
removeOld: function()
{
    while (20 < this.collection.length)
    {
        this.removeOne(this.collection.at(this.collection.length - 1));
    }
},
removeOne: function(model)
{
    model.destroy();
}
this.collection.eachだと、どうもindexがズレてくるようで断念しました。。。

参考:
http://backbonejs.org/#Collection-at
http://backbonejs.org/#Collection-length

backbone.jsのcollectionのmodelを降順でソートする

メモです。

対象の値が数値(あるいは数値に変換出来るもの)であれば、Collection.comparatorのreturnにマイナスをつければ降順ソート出来ました。(まあ、当たり前か。。。)
var XxxList = Backbone.Collection.extend({
    comparator: function(model) {
        return -new Date(model.get("yyyymmdd")).getTime();
    }
});
参考: http://stackoverflow.com/questions/5013819/reverse-sort-order-with-backbone-js

March 1, 2013

Chrome ExtensionでHTMLにスクリプトを書いたらエラー

HTMLのbodyにscriptタグで処理を書いたら以下のエラーが出ました。
Uncaught EvalError: Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: "script-src 'self' chrome-extension-resource:".
manifest.jsonに以下を書いたら解決出来ました。
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'"
参考: http://developer.chrome.com/extensions/contentSecurityPolicy.html

--

追記:
backbone.js + underscore.jsで
<script type="text/template"...
を書くために上記の対応をしました。
<script type="text/javascript"...
については、動きませんでした。外部ファイルにするしか無いのかな。。。