write ahead log

ロールフォワード用

Test::Unitでテストを定義順に実行する

Test::Unitはデフォルトではテストを名前順に実行する.

require 'test/unit'

class TestSample < Test::Unit::TestCase
  def test_B
    puts "B"
  end

  def test_A
    puts "A"
  end
end
$ ruby sample.rb
Loaded suite sample
Started
A
.B
.

Finished in 0.0011721 seconds.
-------------------------------------------------------------------------------------
2 tests, 0 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
-------------------------------------------------------------------------------------
1706.34 tests/s, 0.00 assertions/s

これを定義順にしてやりたいときにはtest_order = :definedという指定が出来るらしい.

require 'test/unit'

class TestSample < Test::Unit::TestCase

  self.test_order = :defined

  def test_B
    puts "B"
  end

  def test_A
    puts "A"
  end
end
$ ruby sample.rb
Loaded suite sample
Started
B
.A
.

Finished in 0.000768 seconds.
--------------------------------------------------------------------------------------
2 tests, 0 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
--------------------------------------------------------------------------------------
2604.17 tests/s, 0.00 assertions/s

うーん, 設計がダサい証拠な気もするけど, 便利だ.

Test::Unitでテスト群の最初と最後にだけメソッドを呼びたい

あんまりよろしくないやり方な気がするけど, 便利な時には便利なので.

標準で用意されているsetupteardownは各テストケースの呼び出し前後に呼び出される.

require 'test/unit'

class TestSample < Test::Unit::TestCase
  def setup
    puts "setup"
  end

  def teardown
    puts "teardown"
  end

  def test_foo
    puts "foo"
  end

  def test_bar
    puts "bar"
  end
end
$ ruby sample.rb
Loaded suite sample
Started
setup    [準備して]
bar
teardown [片付ける]
.setup   [準備して]
foo
teardown [片付ける]
.

Finished in 0.0010129 seconds.
----------------------------------------------------------------------------------------------------
2 tests, 0 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
----------------------------------------------------------------------------------------------------
1974.53 tests/s, 0.00 assertions/s

普通はこれで何の問題もない.

なんだけど, たまーにテストケース全体の最初と最後だけに呼び出したい事がある.

$ ruby sample.rb
Loaded suite sample
Started
setup     [最初に準備して]
bar
.foo
.teardown [最後に片付ける]


Finished in 0.0009669 seconds.
----------------------------------------------------------------------------------------------------
2 tests, 0 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
----------------------------------------------------------------------------------------------------
2068.47 tests/s, 0.00 assertions/s

強引だなぁとは思うけど, TestUnitが最初と最後に呼ぶstartupshutdownを特異クラスでオーバーライドしてやればできるらしい.

require 'test/unit'

class TestSample < Test::Unit::TestCase
  class << self
    def startup
      puts "setup"
    end

    def shutdown
      puts "teardown"
    end
  end

  def test_foo
    puts "foo"
  end

  def test_bar
    puts "bar"
  end
end

まぁ, 原則やるなってことだよね, これ.

便利な時は便利なんだけどなぁ.

Dynamic DNS環境でzoneファイルをバックアップ/移行したい

bind9.xでは動的更新はすぐにはzoneファイルへ反映されず, jnlファイルへ書き込まれる.

移行とかバックアップとかどうやんだと思っていたが, どうも一度freezeすればzoneへ書き戻しされそう.

# 一度動的更新を止める
rndc freeze ddns.example.com
[何らかのコマンドでコピーして移行]
# 動的更新を再開
rndc thaw ddns.example.com

named.confに以下の様に書いておけばtext形式でzoneファイルを管理するのだけど, 少なくてもtext形式になっている限りでは確認できた.

masterfile-format text;

raw形式の場合は下記で確認できるらしい.

# named-checkzone -D -f raw <zone名> <zoneファイル名>
named-checkzone -D -f raw ddns.example.com ddns.example.com.zone

もっとも, 無停止で移行の場合はゾーン転送を利用するのだろうけど.

dig @localhost ddns.example.com. axfr

WSL(ubuntu)でRails環境を構築した時のコマンド履歴

もうずいぶん触ってないし, Railsチュートリアルやろうかと思ってハマったので.

historyを加工して書き留めておく.

$sudo apt install ruby-dev
$sudo apt-get install libxml2 libxml2-dev libxslt-dev
$sudo apt install ruby-bundler
$bundle config build.nokogiri "--use-system-libraries --with-xml2-include=/usr/include/libxml2"
$sudo apt install gcc
$sudo apt install zlib1g-dev
$sudo apt install libxslt-dev
$sudo apt install build-essential libssl-dev libreadline-dev 
$sudo gem install rails -v 5.1.4
$rails -v
$sudo gem install bundler
$sudo apt install libsqlite3-dev
$sudo apt install nodejs

ここまでやってようやくrails new sampleができた.

[追記]

rails new すると

warning: Insecure world writable dir /mnt/c in PATH, mode 040777

といったメッセージが出てくる.

どうも原因としては権限がありすぎるディレクトリに対してRubyが気を利かせてくれているということらしいのですが, 毎度これが出てくるのも困る.

CLI上での相互運用も考えていないので, 思い切ってUbuntu側ではWindowsの環境変数を削除してしまいました.

具体的には.bashrcの先頭に以下を追加.

export PATH="$(echo "$PATH" | sed -r -e 's;:/mnt/[^:]+;;g')"

参考

参考ページまんまです.

[さらに追記]

mysql使うなら以下も.

$ sudo apt install libmysqld-dev

VB.NETで次の指定曜日の日付を取得する

ちょいちょい使うのでメモ.

''' <summary>
''' 指定日の次の指定曜日の日付を返す
''' </summary>
''' <param name="d">次の曜日を調べる起算日</param>
''' <param name="day">日付を得たい次の曜日</param>
''' <returns>d日から始めて次のday曜日の日付</returns>
Private Function getNextDayOfWeek(ByVal d As Date, ByVal day As System.DayOfWeek) As Date
    '定数DayOfWeekは0起算の連続した数値なので当週の曜日指定日付は
    'd.AddDays(day - d.DayOfWeek)で導出可能

    '直近のday曜日を導出してその1週間後
    Return d.AddDays(day - d.DayOfWeek).AddDays(7)
End Function

VB.NETでのフォルダパスの組立と特殊フォルダ名の取り扱い方

パスをC:\とか指定するのは流石にないだろうと.

いい機会なのでまとめようと思う.

パスの組立

パスの取り扱いは専用のクラスが用意されている.

MS Developer Network - Pathクラス

パスの組立はCombineメソッドを使うといい感じ.

path = System.IO.Path.Combine(path, "hoge.csv")

このメソッドはオーバーロードされていて, 4引数まで使える.

path = System.IO.Path.Combine(path, "dir1", "dir2", "dir3", "hoge.csv")

特殊フォルダの場所の取得

Windowsでよく使う特殊フォルダは取得する方法が用意されている.

Environment.GetFolderPath メソッド

こんな感じで簡単にデスクトップのパスとかを取れる.

System.Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory)

なのでデスクトップにファイルを保存する時も簡単.

デスクトップに"test.csv"を保存したいときにパスを作ると以下の感じ.

Dim path As String = System.IO.Path.Combine(
    System.Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), "test.csv")

特殊フォルダは定数になっているので必要なものを使うだけで良い.

Environment.SpecialFolder 列挙型

余談

他にも便利なメソッドがいっぱい.

メソッド名 機能
GetExtension(String) 指定したパス文字列の拡張子を返す
GetFileName(String) 指定したパス文字列のファイル名と拡張子を返す
GetFileNameWithoutExtension(String) 指定したパス文字列のファイル名を拡張子を付けずに返す

VB.NETでCSVを読み込む・書き込む

簡単そうでエスケープ(,入力が必要な際のダブルクオーテーションによる例外化)処理やデリミタ変更(TSV)が以外と面倒なCSV処理.

標準ライブラリでやってみました.

書いてからEncoding指定もパラメータ化すれば良かったと思いましたが, そもそもクラス化してライブラリにした方がいいでしょうね.

読み込み

ファイル名指定

TextFieldParserを使うとファイル名も指定出来て楽.

けど, 後述するStream渡しの方が単機能になって個人的には好み.

''' <summary>
''' CSVファイル名を渡して解析し, 結果の2次元のリストを返す
''' </summary>
''' <param name="fileName">解析するCSVファイル名</param>
''' <returns>CSVの行列を含む2次元のリスト</returns>
Private Function LoadCSVFile(ByVal fileName As String) As List(Of List(Of String))
    '=========================================
    '結果返答用のリストを準備
    '=========================================
    Dim ret As New List(Of List(Of String))

    '=========================================
    'ファイル解析用にTextFieldParserを用意
    '=========================================
    'ファイル名から指定する場合
    Dim parser As New FileIO.TextFieldParser(fileName,
                             System.Text.Encoding.GetEncoding("Shift_JIS"))

    '=========================================
    'Parserの設定
    '=========================================
    '区切り文字形式指定
    parser.TextFieldType = FileIO.FieldType.Delimited
    'デリミタを指定(CSVなら, TSVならvbTab)
    parser.SetDelimiters(",")
    'ダブルクオーテーションでエスケープするフィールドがあるならTrue
    parser.HasFieldsEnclosedInQuotes = True
    '区切り文字前後の空白スペースを除去するならTrue
    parser.TrimWhiteSpace = True

    '=========================================
    'Parse開始
    '=========================================
    'ファイル終端まで繰り返す
    While Not parser.EndOfData
        '結果を入れるリスト(1行分)
        Dim ret_row As New List(Of String)

        '1行読み出す => 各列の配列になって取得できる
        Dim row As String() = parser.ReadFields()

        '各列を読み出し
        For Each field As String In row
            '各列分リストへ入れる
            ret_row.Add(field)
        Next
        '出来た1行分をリストへ入れる
        ret.Add(ret_row)
    End While

    Return ret
End Function

IO.Streamを指定

ファイル名ではなくIO.Streamを指定してやる.

こっちの方が外部IOが無い分, シンプルで良いと思う.

''' <summary>
''' CSVのIO.Streamを渡して解析し, 結果の2次元のリストを返す
''' </summary>
''' <param name="csvStream">解析するCSVのIOStream</param>
''' <returns>CSVの行列を含む2次元のリスト</returns>
Private Function LoadCSVFromStream(ByVal csvStream As IO.Stream) As List(Of List(Of String))
    '=========================================
    '結果返答用のリストを準備
    '=========================================
    Dim ret As New List(Of List(Of String))

    '=========================================
    'ファイル解析用にTextFieldParserを用意
    '=========================================
    Dim encoding As System.Text.Encoding = System.Text.Encoding.GetEncoding("Shift_JIS")
    Dim parser As New FileIO.TextFieldParser(csvStream, encoding)

    '=========================================
    'Parserの設定
    '=========================================
    '区切り文字形式指定
    parser.TextFieldType = FileIO.FieldType.Delimited
    'デリミタを指定(CSVなら, TSVならvbTab)
    parser.SetDelimiters(",")
    'ダブルクオーテーションでエスケープするフィールドがあるならTrue
    parser.HasFieldsEnclosedInQuotes = True
    '区切り文字前後の空白スペースを除去するならTrue
    parser.TrimWhiteSpace = True

    '=========================================
    'Parse開始
    '=========================================
    'ファイル終端まで繰り返す
    While Not parser.EndOfData
        '結果を入れるリスト(1行分)
        Dim ret_row As New List(Of String)

        '1行読み出す => 各列の配列になって取得できる
        Dim row As String() = parser.ReadFields()

        '各列を読み出し
        For Each field As String In row
            '各列分リストへ入れる
            ret_row.Add(field)
        Next
        '出来た1行分をリストへ入れる
        ret.Add(ret_row)
    End While

    Return ret
End Function

StringをIO.Streamへ変換してやれば文字列渡しでも使えるし.

''' <summary>
''' CSVのStringを渡して解析し, 結果の2次元のリストを返す
''' </summary>
''' <param name="csvStr">解析するCSVのString</param>
''' <returns>CSVの行列を含む2次元のリスト</returns>
Private Function LoadCSVFromString(ByVal csvStr As String) As List(Of List(Of String))
    'Encoding指定し, StringをStreamへ変換
    Dim encoding As System.Text.Encoding = System.Text.Encoding.GetEncoding("Shift_JIS")
    Dim stream As New IO.MemoryStream(encoding.GetBytes(csvStr))

    'Streamを2次元のリストにして返す
    Return LoadCSVFromStream(stream)
End Function

書き込み

読み込みに比べれば随分楽です.

''' <summary>
''' Streamに対してCSVListの内容をCSV形式で書き込みます
''' </summary>
''' <param name="stream">書き込み先Stream</param>
''' <param name="CSVList">書き込む内容の行/列の2次元List</param>
''' <param name="delimiter">区切り文字</param>
''' <returns>書き込み先Stream</returns>
Private Function WriteCSV(ByVal stream As IO.StreamWriter, CSVList As List(Of List(Of String)), ByVal delimiter As String) As IO.StreamWriter
    For i As Integer = 0 To CSVList.Count - 1
        Dim row As List(Of String) = CSVList(i)

        For k As Integer = 0 To row.Count - 1
            '列の値
            Dim col As String = row(k)

            'デリミタが含まれていればエスケープ
            If col.Contains(delimiter) = True Then
                'ダブルクオーテーションが含まれているか確認
                ' => 2重化してエスケープ
                If col.Contains("""") = True Then
                    col = col.Replace("""", """""")
                End If
                '両端にダブルクオーテーションを入れてエスケープ
                col = """" & col & """"
            End If

            '列の値を書き込み
            stream.Write(col)

            '最終列でなければデリミタ挿入
            If k <> row.Count - 1 Then
                stream.Write(delimiter)
            End If
        Next
        '最終行でなければ改行を書き込み
        If i <> CSVList.Count - 1 Then
            stream.Write(vbCrLf)
        End If
    Next

    Return stream
End Function

使う時はこんな感じ.

''' <summary>
''' 試しに書き込み
''' </summary>
Private Sub testCSVWrite()
    '=========================================
    'テスト用のデータ作成
    '=========================================
    Dim list As New List(Of List(Of String))

    Dim r1 As New List(Of String)
    r1.Add("abc")
    r1.Add("def")
    list.Add(r1)

    Dim r2 As New List(Of String)
    r2.Add("""abc""")
    r2.Add("def")
    list.Add(r2)

    Dim r3 As New List(Of String)
    r3.Add("test,string")
    r3.Add("""zxd,")
    list.Add(r3)

    '=========================================
    '書き込み
    '=========================================
    'エンコーディング指定
    Dim encoding As System.Text.Encoding = System.Text.Encoding.GetEncoding("Shift_JIS")
    '保存先パス
    Dim path As String = System.IO.Path.Combine(
        System.Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory),
        "test.csv")
    '書き込み用のStreamWriterを準備
    '第一引数でパス指定
    Dim sw As New IO.StreamWriter(path, False, encoding)
    '書き込み
    WriteCSV(sw, list, ",")
    '閉じる
    sw.Close()
End Sub