MIRACLE
メールサービス申込 ユーザー登録 パートナー情報
お問い合わせ FAQ サイトマップ
MIRACLE LINUXの特長 製品紹介 サービス案内 購入 サポート 技術フォーラム

プロフィール

日本発のリナックス企業、ミラクル・リナックスで奮闘する社員のブログです。

ミラクル関連リンク

採用情報

サイト検索

最近のトラックバック

2009年5月

          1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31            

« C++入門 | メイン | RPMコマンド(その4) カーネルモジュールのバージョン? »

ruby ことはじめ - 第5回「'プログラム'クラスを設計&実装してみよう」

こんにちは。先日ジムのプールでオバちゃんにナンパされそうになったkyagiです。

今回は実際のお仕事で書いているコードから引用して話を進めます(例外処理がまだまだ未実装なのでそのへんのツッコミはご容赦ください(>_<))。現在kyagiはあるプロジェクトでプログラムを管理して実行するスクリプトをスクラッチから作成しています(LTPもautotestもテストプログラムの集合があり、それを一括して実行するスクリプトがありますね)。

設計にあたり、まず何がクラスの単位になるのかを考えてみました。これはもう「プログラム」しかないでしょう(あたりまえのように書いていますが実はこの単位を決めるのに結構悩みました (^_^;) 。

次に「プログラム」クラスのメンバとメソッドを考えてみました。このクラスのインスタンスはひとつひとつのプログラムです。プログラムがもつ属性と してexecve(2)を参考に「起動されたディレクトリ」「自分自身のファイル名(パス)」「引数」としてみました。そうすると「コマンドライン」も自動的に生成できちゃうのでこれも追加です。メソッドとしては「自分を実行す る」ことですね。ここではシンプルにKernel#systemを使うことにしました。「プログラムの出力はどうするんぢゃ」というそこの人、慌てないで ください。お楽しみはあ・と・で(はあと)。

class Program
  attr_accessor:dir_root
  attr_accessor:filename
  attr_accessor:arguments
  attr_accessor:commandline

  def initialize(filename, *arguments)
    @dir_root = Dir.pwd
    @filename = filename
    @arguments = arguments
    @commandline = [filename, arguments].flatten.join(" ")
  end

  def execute
    system(commandline)
  end
end

さらにこのクラスを継承したサブクラスを作成し、executeメソッドをログ取得用にオーバーライドしてしてみます。標準出力、標準エラー両方のログを別々にとるには open3ライブラリを使うと非常に簡単です*1。他にも自分が取得したい情報をメンバとして追加しておきます。

require 'open3'

class CjkProgram < Program
  #FIXME: TestProgram Class doesn't need kv_expected
  #FIXME: CompareProgram Class doesn't need kv_now
  attr_accessor:kv_now      # kernel_version_now
  attr_accessor:kv_expected  # kernel_version_expected
  attr_accessor:dir_result
  attr_accessor:dir_testcases
  attr_accessor:log_dir
  attr_accessor:log_stdout
  attr_accessor:log_stderr
  attr_accessor:log_status
  attr_accessor:syscallname

  def initialize(filename, *arguments)
    super
    @kv_now = `uname -r`.chomp!
    @kv_expected = nil
    @dir_result = [@dir_root, "results"].join('/')
    @dir_testcases = [@dir_root, "testcases"].join('/')
    @log_dir = nil
    @log_stdout = nil
    @log_stderr = nil
    @log_status = nil
    @syscallname = File.dirname(@filename).sub(@dir_testcases, "")
  end

  #FIXME: Replace this code, because 'open3' library cannot get exit_status.
  def execute
    Open3.popen3(@commandline) do |stdin, stdout, stderr|
      stdin.close
      File.open(@log_stdout, "w") do |f|
        f.puts stdout.read
      end
      File.open(@log_stderr, "w") do |f|
        f.puts stderr.read
      end
    end
  end
end

最後にこのサブクラスを継承したサブクラスを作り(実際に使用するのはこのクラスになります)、実行速度の測定やインスタンス数の記録もできるように拡張します。またこのクラスのみで使用するメソッドも追加し、外部 から呼び出す必要のないものはついでに private 属性にしておきます(ちょっとかっこいい)。ここまででお気付きのように上位のクラスで定義したメンバやメソッドはオーバーライドしない限り有効で super で呼び出せます。Program > CjkProgram > TestProgram と3世代にわけてクラスを設計したのは後の再利用と拡張性を考え、上位のクラスは汎用的に使えるようにしたかったからです。

require 'fileutils'

class TestProgram < CjkProgram

  attr_accessor:time_start
  attr_accessor:time_end
  @@count_instance = 0
  @@count_execute = 0
  @@time_start_first = 0

  def initialize(filename, *arguments)
    super
    @log_stdout = "tout.log"
    @log_stderr = "terr.log"
    @log_status = "tstatus"
    @time_start = nil
    @time_end = nil
    @@count_instance += 1
  end

  def execute
    set_log_dir()
    make_result_dir()

    @log_stdout = [@log_dir, @log_stdout].join('/')
    @log_stderr = [@log_dir, @log_stderr].join('/')
    @log_status = [@log_dir, @log_status].join('/')

    @@count_execute += 1

    print "Executing #{@@count_execute}/#{@@count_instance}\n"
    print "TestProgram: #{@filename}\n"

    @time_start = Time.now
    if (@@count_execute == 1)
      @@time_start_first = @time_start
    end

    super

    @time_end = Time.now
    print "ExecutionTime: ", @time_end - @time_start, "\n\n"
    if (@@count_execute == @@count_instance)
      print "Total ExecutionTime: ", @time_end - @@time_start_first, "\n\n"
    end
  end

  (...snip...)
  def get_time_str
    Time.now.strftime("%Y%m%d%H%M%S")
  end
  private:get_time_str
  (...snip...)

  def set_log_dir
    @log_dir = [@dir_result, @kv_now, get_time_str(), @syscallname].join("/")
  end
  private:set_log_dir

  def make_result_dir
    FileUtils.mkdir_p(@log_dir)
  end
  private :make_result_dir

end

ここで実際にクラスを使用している箇所をあげます。以下は TestProgramクラスのインスタンスを次々に作成してリストにした後で一括実行しています。オブジェクトが持つべき情報、やるべき仕事はクラスで定義してあるのでコード がとてもシンプルになりました。このへんオブジェクト指向の醍醐味を肌で感じている今日このごろです。:-)

Class Framework
  (...snip...)
  def self.create_tp_list(path)
    tp_list = Array.new()
    begin
      f = open(path)
      while line = f.gets
        path = File.expand_path(line.chomp!)
        if File.exists?(path) then
          tp = TestProgram.new(path)
          tp_list.push(tp)
        end
      end
    rescue Errno::ENOENT => err
      errmsg err
      exit 1
    ensure
      f.close
    end
    tp_list
  end # def

  def self.exe_tp_list(tp_list)
    tp_list.each do |tp|
      tp.execute
    end
  end
  (...snip...)
end

*1 でも open3 ライブラリでは終了ステータスが取れない仕様だったりするので現在他の方法を模索中です(T_T)

トラックバック

このページのトラックバックURL:
http://www.typepad.jp/t/trackback/4447/6748848

このページへのトラックバック一覧 ruby ことはじめ - 第5回「'プログラム'クラスを設計&実装してみよう」:

コメント

コメントを投稿

会社情報 採用情報 個人情報保護方針 商標等取り扱い事項 English
Copyright(c)2000-2006 MIRACLE LINUX CORPORATION. All Rights Reserved.