kkAyatakaのメモ帳。

誰かの役に立つかもしれない備忘録。

Creative Commons Licenseのテキストファイル

GitHubで適用する時など用。身も蓋もないけど「plaintext」で検索かければ引っかかる。

公式サイトのフッターの検索で「plaintext」を検索してもOK。

f:id:kkAyataka:20210501211546p:plain:w300

URLに「.txt」をつける

URLを見ればわかるが、Legal Text / LicenseのURLに「.txt」を付ければ辿り着ける。

以下のように、選択サイト -> サマリー -> 全文 -> テキストファイル。

  1. https://creativecommons.org/choose/
  2. https://creativecommons.org/licenses/by/4.0/
  3. https://creativecommons.org/licenses/by/4.0/legalcode
  4. https://creativecommons.org/licenses/by/4.0/legalcode.txt

CC0のテキストファイル

CC0の場合も同じ構造になっている。パブリックドメインにする場合はこっち。

CC0の場合はGitHubリポジトリを作るときにも選べる。

f:id:kkAyataka:20210501213401p:plain:w300

plusctx (処理履歴の保存と参照)

ライブラリというよりは実装アイデアという感じ。ロギングの課題を解決するために考えていところ、構造的に割と面白かったので組んでみた。

組んだ後でスタックトレースで良いのでは?とも思ったけど、まあ、マルチプラットフォームで取ったり、整形したり、任意の情報乗っけたりとか考えたら、一応用途はありそう。

サンプル

  • 関数の冒頭でPLUSCTX_CTX("name");としてコンテキストを定義する
    • コンテキストはスレッド毎 (thread_local) にスタックとして保存される
    • ブロックコープを抜けると (関数を抜けると) スタックから破棄される
  • サブ関数内でコンテキストスタックを確認すると、上位の (呼び出し元) の情報を得ることができる
#include "plusctx/plusctx.hpp"

#include <iostream>
#include <sstream>

// コンテキストスタックから文字列を生成
// スタックはスレッド毎に管理
std::string get_ctx_string() {
    const auto stack = plusctx::get_context_stack();

    std::ostringstream ss;
    for (const auto c : stack) {
        ss << ">" << c->name;
    }

    return ss.str();
}

void sub1();

// main
int main() {
    PLUSCTX_CTX("main");

    // mainを詰んだのでmainだけ
    std::cout << get_ctx_string() << std::endl; // >main

    sub1();

    // コンテキストの寿命はブロック単位なので、
    // sub1コンテキストは削除されてる
    std::cout << get_ctx_string() << std::endl; // >main
}

void sub1() {
    PLUSCTX_CTX("sub1");

    // mainから呼び出されたことがわかる
    std::cout << get_ctx_string() << std::endl; // >main>sub1
}

plusctx::Contextクラス

ロギング用途を想定しているので、コンテキストには関数名やファイル名などを保存している。コンテキスト情報の取り扱いについては既定していないので、どう使うかは別の問題。

// 任意の名前だけでなく、関数名やファイル名も保存する
class Context {
public:
    Context(
        const std::string name,
        const std::string func_name,
        const std::string rich_func_name,
        const std::string file_name,
        const std::size_t line_no
    );

// 簡易にインスタンス化できるようにマクロを準備
#define PLUSCTX_CTX(name) plusctx::Context plusctx_ctx__( \
    name, \
    __func__, \
    __FUNCSIG__, \
    plusctx::detail::shorten_file_name(__FILE__), \
    __LINE__)

その他

  • 個人的に使うかは微妙なラインなのだけど、アイデアは残しておきたかったので組んでみた
  • 実際の運用場面ではアプリケーション段階で決めることが多い。継承して拡張しても良いし、同コンセプトで定義しなおすこともありそう
  • コンテキストの生成時 (コンストラクタ) トレースログ出すとかもあり
  • アプリケーションデータの伝搬のほうも考えたけど、こっちは全体デザインに影響するので適用範囲が限られるのと、簡単にグローバル変数になるので運用つらそう

Google Testでデータドリブンテスト

他の (言語の) テスティングフレームワークで、同一のテストを異なるデータで実行するデータドリブンテストの機能があって、これGoogle Testでも無いんかいな、と調べたところあった。

Google Testでは「Value-Parameterized Tests」として説明されている。説明内に「data-driven testing」って記述があるので、検索すれば引っかかる。

Value-Parameterized Tests

使用する機能さえ把握してしまえば難しくない。シンプルに書けるようにうまく設計されてる。

// テストデータクラス
struct MyTestParam {
    int v1;
    int v2;
    int ok_v;

    MyTestParam(
        const int v1,
        const int v2,
        const int ok_v
    ) : v1(v1),
        v2(v2),
        ok_v(ok_v) {
    }
};

// TestWithParamにテストデータのクラスを渡して、継承する
class MyTest : public testing::TestWithParam<MyTestParam> {
};

// TEST_Pでテストを書く
// GetParamでテストデータクラスのインスタンスを得る
TEST_P(MyTest, test) {
    const auto p = GetParam();

    EXPECT_EQ(p.v1 + p.v2, p.ok_v);
}

// データをインスタンス化して、テストを作る
INSTANTIATE_TEST_SUITE_P(OkTest, MyTest,
    testing::Values(
        MyTestParam(1, 2, 3),
        MyTestParam(2, 3, 5)
    )
);

実行結果。

Note: Google Test filter = */MyTest.*
[==========] Running 2 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 2 tests from OkTest/MyTest
[ RUN      ] OkTest/MyTest.test/0
[       OK ] OkTest/MyTest.test/0 (0 ms)
[ RUN      ] OkTest/MyTest.test/1
[       OK ] OkTest/MyTest.test/1 (0 ms)
[----------] 2 tests from OkTest/MyTest (2 ms total)

[----------] Global test environment tear-down
[==========] 2 tests from 1 test suite ran. (3 ms total)
[  PASSED  ] 2 tests.

OkTest/MyTest.test/0といった感じで出力される。この文字列でフィルタ出来るので、--gtest_filter=*/MyTest.*/0で、1つ目のデータだけで実行できる。

テストデータに名前を付ける (出力する)

/0/1の部分の文字列を置き換える。仕組み上任意の文字列...とまではできないのだけど、ある程度読みやすくなる。

簡単なのがテストデータに対してoperator<<を用意してあげる方法。合わせて、INSTANTIATE_TEST_SUITE_Pの最後に、testing::PrintToStringParamName()を渡す。

struct MyTestParam {
    std::string desc; // 追加
    int v1;
    int v2;
    int ok_v;

    MyTestParam(
        const std::string & desc, // こっちも更新
        const int v1,
        const int v2,
        const int ok_v
    ) : desc(desc),
        v1(v1),
        v2(v2),
        ok_v(ok_v) {
    }
};

// operator<<を作って、単純にdescを返す
std::ostream & operator<<(std::ostream & stream, const MyTestParam & p) {
    return stream << p.desc;
}

class MyTest : public testing::TestWithParam<MyTestParam> {
};

TEST_P(MyTest, test) {
    const auto p = GetParam();

    EXPECT_EQ(p.v1 + p.v2, p.ok_v);
}

INSTANTIATE_TEST_SUITE_P(OkTest, MyTest,
    testing::Values(
        MyTestParam(
            "1p2eq3", // テスト名をつける
            1, 2, 3
        )),
    testing::PrintToStringParamName() // 追加
);

実行結果。/0だったところに指定したテスト名が出力されている。

Note: Google Test filter = */MyTest.*
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from OkTest/MyTest
[ RUN      ] OkTest/MyTest.test/1p2eq3
[       OK ] OkTest/MyTest.test/1p2eq3 (0 ms)
[----------] 1 test from OkTest/MyTest (1 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (2 ms total)
[  PASSED  ] 1 test.

テストデータだけだと何を狙ったデータなのかの情報がロストする。コメントでもいいけど、なんとなく把握できるテスト名を適当につけとくと、コード的にもテスト結果的にも内容を把握する手助けになる。

sphinxcontrib-xlsxtableに行・列指定オプションを追加

  • 使用する / 使用しない、行 / 列を指定するオプションを追加
  • v1.0.0としてリリース

Excelファイルから表を作るSphinx拡張について、v1.0.0としてリリースしました。 が、足りない機能があるような無いような...(まあ、必要性がはっきりしたら追加すれば...)

使用する行 / 列と使用しない行 / 列の指定

include-rowsexclude-rowsinclude-columnsexclude-columnsを使って、指定できるようにしました。

.. xlsx-table::
   :file: path/to/xlsx/file.xlsx
   :include-rows: 1-2 4 8
   :exclude-rows: 3 5-7
.. xlsx-table::
   :file: path/to/xlsx/file.xlsx
   :include-columns: A-B 4
   :exclude-columns: C 5-6

矩形範囲指定のオプションは実装していませんが、オプションの組み合わせで同等のことはできます。

.. B2-D3の範囲

.. xlsx-table::
   :file: path/to/xlsx/file.xlsx
   :include-rows: 2-3
   :include-columns: B-D

表の分割表示を想定

Excelを表として取り込むときに分割して取り込むのを想定していました。 可読性もですが、特にPDF化を想定した場合、ページに収める細工が必要そうで。

.. xlsx-table:: 表1
   :file: path/to/xlsx/file.xlsx
   :include-columns: A-G

.. xlsx-table:: 表2
   :file: path/to/xlsx/file.xlsx
   :include-columns: H-N

また、既存のExcelファイルをSphinx化するときに都合よく取り出したり。 メタデータも同時に記載されている場合に、メタデータとデータの表を分けるとかはありかと。

includeかexcludeか

include指定は明示的ですが、Excelファイルを更新しても、自動更新されなくなります。 逆にexcludeはExcelファイルにしたがって更新されますが、表示が崩れるとも言えます。 用途に合わせた指定が必要です。

リンク

Sphinx拡張を作ったときに参考にしたモノ

sphinxcontrib-xlsxtableを作る際に参考にしたところ。 最終的にはdocutilsのソースコードまで見ました。

あと、開発時は見てないけど、以下がもっそい参考になりそう。

いろいろとWebを漁りながらやっていましたが、どうも非公式のドキュメントを結構見てたみたいです。 リファレンスに全然書いてねぇ...とか思ってたけど、そもそも公式じゃなかった。 もうちょっとdocutilsの公式リファレンスを漁ったほうがよかった。

sphinxcontrib-xlsxtableはいうなれば、CSVファイルがExcelになったようなモノなので、 CSV Table DirectiveとTable Directiveを参考にしました。

特にソースを追いかけたのはTable DirectiveのRSTTableクラス。 作成したクラスの継承元でもありますが、そもそもTable Directiveがかなりシンプルな構文なので、 参考にしやすいです。

# https://sourceforge.net/p/docutils/code/HEAD/tree/trunk/docutils/docutils/parsers/rst/directives/tables.py
# RSTTable classのrunメソッド
def run(self):
    ...

    # Table Captionの取得とかはそのまま流用
    title, messages = self.make_title()
    # この辺を順番にいじったり、デバッグしながら動作を確認
    node = nodes.Element()
    self.state.nested_parse(self.content, self.content_offset, node)

    ...

    table_node = node[0]
    table_node['classes'] += self.options.get('class', [])
    self.set_table_width(table_node)
    if 'align' in self.options:
        table_node['align'] = self.options.get('align')
    tgroup = table_node[0]

    ...

    self.add_name(table_node)
    if title:
        table_node.insert(0, title)
    return [table_node] + messages

見返すと結構見るべきドキュメントを取りこぼしてる感じだけど、 当初は理解の仕方を理解してなかったところがあるので、理解するのは無理だったかも。 一から理解して~というよりも、とりあえず動かしてみたいってのも強かった。

XcodeとgcovrでC++コードのカバレッジを計測する

Xcode単体では可視化できないブランチカバレッジを取り扱える。 Xcodeへのシームレスな統合は失われるが、設定・実行は簡単で、手順はシンプルになる。

f:id:kkAyataka:20200617210900j:plain

環境

手順

  1. Xcodeカバレッジファイルの出力設定を有効にする
  2. テストを実行する
  3. gcoverをインストールして実行する

Xcodeカバレッジファイルの出力設定を有効にする

とりあえずはXcodeの出力ロケーションを相対にしておく。 カバレッジファイルがDerivedData (の結構深いところ) にできるので、相対的に参照できるパスが何かと都合が良い。

f:id:kkAyataka:20200617211038j:plain

2つのビルド設定を有効にする。

  • Instrument Program Flow (GCC_INSTRUMENT_PROGRAM_FLOW_ARCS)
  • Generate Legacy Test Coverage Files (GCC_GENERATE_TEST_COVERAGE_FILES)

Instrument Program Flow (GCC_INSTRUMENT_PROGRAM_FLOW_ARCS)

gcc-fprofile-arcs。flowとかarcsで検索すると出てくる。

f:id:kkAyataka:20200617211210j:plain

Generate Legacy Test Coverage Files (GCC_GENERATE_TEST_COVERAGE_FILES)

gcc-ftest-coverage。coverageで検索すると出てくる。 Xcode 11だとLegacy扱い。標準のはclangの設定。

f:id:kkAyataka:20200617211414j:plain

テストを実行する

unit_testに設定したので、単純にunit_testを実行する。 失敗するようならクリーンにするとかDerivedDataを消すとかすれば解消されると思われ。

gcovrをインストールして実行する

Pythonのツールなので、pipでインストール。

% pip install gcovr

試した時はプロジェクト以下に仮想環境を作ったけど、流石にグローバルインストールでいい気がしてる。

gcovrを適当に呼び出すだけで検出してくれる (この時DerivedDataにできるカバレッジファイルを参照するのに、相対だと都合が良い)。 ただ、不必要な (例えばテストコード自体の) カバレッジまで出力されるので、 適当にフィルタされるようにオプションを指定して実行する。

% gcovr -f '.*plusaes.hpp' -r .
------------------------------------------------------------------------------
                           GCC Code Coverage Report
Directory: .
------------------------------------------------------------------------------
File                                       Lines    Exec  Cover   Missing
------------------------------------------------------------------------------
include/plusaes/plusaes.hpp                  372     365    98%   212,222,407,432,451,558,712
------------------------------------------------------------------------------
TOTAL                                        372     365    98%
------------------------------------------------------------------------------

HTML形式にも出力できる。結果を見て何か作業する時はこっち。ブランチカバレッジの数値も自動で出る。

% gcovr -f '.*plusaes.hpp' -r . --html --html-details -o cov/coverage.html

f:id:kkAyataka:20200617210900j:plain

--html-detailsをつけるとソースコードの詳細をビジュアルで確認できる。

f:id:kkAyataka:20200617210758j:plain

XcodeでC++のカバレッジを計測する

厳密にはラインカバレッジらしい。また、ブランチカバレッジは取れない。

f:id:kkAyataka:20200614170723j:plain

環境

ヘッダーオンリーライブラリとして開発したファイルの単体テストカバレッジを測ります。

測定方法

  1. Test TargetをObjective-Cで作る
  2. テストコードを作成する
  3. カバレッジ計測オプションを有効にする
  4. hppファイルをコンパイル対象にする
  5. 実行して結果を見る

Test TargetをObjective-Cで作る

Test Navigatorから新しいTest Targetを作成する。

f:id:kkAyataka:20200614163744j:plain

Test言語はObjective-C。あとで設定を変えるが、実行するのはObjective-C++としてビルドする。単体テストGoogle Testで記述してあるので、それをなるべくそのまま実行できるようにする。

f:id:kkAyataka:20200614164121j:plain

テストコードを作成する

テストコードを編集します。ここではGoogle Testのメイン実行関数をそのまま呼び出すようにしています。また、この時Unit Testのコードが実行されるように、Test Targetの設定 (ソースファイルの追加やヘッダー検索パスの設定) なども行います。

- (void)testExample {
    NSArray * args = [[NSProcessInfo processInfo] arguments];
    int argc = (int)args.count;
    char const * argv[10] = {};
    for (int i = 0; i < argc; ++i) {
        argv[i] = [[args objectAtIndex: i] cStringUsingEncoding: NSUTF8StringEncoding];
    }

    // Google Testを実行
    testing::InitGoogleTest(&argc, (char**)argv);
    RUN_ALL_TESTS();
}

この時、C++のコードをそのまま呼び出すことになるため、テストソースのファイルタイプをObjective-C++に設定します。

f:id:kkAyataka:20200614165507j:plain

カバレッジ計測オプションを有効にする

Unit Testのスキーマ設定画面を開いて、テストのところに作成したTest Targetを追加します。

f:id:kkAyataka:20200614213640j:plain

そして、カバレッジ計測を有効にするためにOptionsを開いて、Code Coverageを有効にします。

f:id:kkAyataka:20200614165057j:plain

hppファイルをコンパイル対象にする

計測対象がhppなどのヘッダファイルの場合は、コンパイル対象として設定します。コンパイル対象にしないとカバレッジ計測の対象となりません。

f:id:kkAyataka:20200614170443j:plain

実行して結果を見る

Unit Test (Test Targetではなく) を実行します。Build・実行ボタンを長押しすると実行方法が選べるので、ここでテストを選ぶと、スキーマで設定したテストが実行されます。

実行後、結果画面から結果を見ます。「By Group表記」の方が個人的には見やすい。

「Show Test Bundles」を有効にすると、Test Targetで実装したコードのカバレッジを確認できます。C++ソースのカバレッジが計測されていることが確認できます。

f:id:kkAyataka:20200614213750j:plain

この状態でソースコードをひらけば、実行されていない箇所をハイライトで確認することができます。

f:id:kkAyataka:20200614171016j:plain