自動テスト

GDAL には,Python (pytest 経由) と C++ (gtest 経由) の組み合わせを使用して実装された包括的なテストスイートが含まれています.

CMake を使用して GDAL をビルドした後, ctest -v --output-on-failure を使用して完全なテストスイートを実行できます.これにより,テストがインストールされたシステムコピーではなく,ビルドされたバージョンの GDAL で実行されるように環境変数が自動的に設定されます.

ctest を使用してテストのサブセットを実行する

ctest によって認識されている完全なテストスイートのセットは,``ctest -N`` を実行して表示できます.

ctest-R 引数を使用してテストのサブセットを実行できます.これにより,指定された正規表現を使用してテストを選択できます.たとえば,``ctest -R autotest`` は Python ベースのテストを実行します.

-E 引数を使用して正規表現を使用してテストを除外できます.たとえば,``ctest -E gdrivers`` はドライバーテストのスイートを除外します.

pytest を使用してテストのサブセットを実行します

ctest によって公開されるテストサブセットはまだかなり大きく,いくつかは実行に数分かかる場合があります.より高いレベルの特異性が必要な場合,``pytest`` を直接呼び出して,グループのテストまたは個々のテストを実行できます.``pytest`` を実行する前に,システムバージョンではなく,開発ビルドの GDAL がテストされるように, development environment variables を設定することが重要です.

その後,``pytest`` を呼び出すことで,個々のファイルでテストを実行できます.Linux および MacOS ビルドでは,テストはビルドディレクトリにシンボリックリンクされているため,ビルドディレクトリから次のコマンドを実行することでこれを行うことができます:

pytest autotest/gcore/vrt_read.py

Windows では,テストファイルはソースツリーに残りますが,pytest の設定ファイル pytest.ini はビルドディレクトリにのみ利用可能です.これに対応するために,上記のコマンドは次のように変更されます:

pytest -c pytest.ini ../autotest/gcore/vrt_read.py

個々のテストファイル内のテストのサブセットは,``pytest`` の -k 引数に正規表現を指定することで実行できます.

pytest autotest/gcore/vrt_read.py -k test_vrt_read_non_existing_source

pytest は,テストを実行せずにテストに関する情報を報告することもできます.たとえば,名前に "tiff" が含まれるテストをリストするには:

pytest --collect-only autotest -k tiff

警告

すべての Python テストを独立して実行することはできません.一部のテストは,同じファイル内の前のテストによって設定された状態に依存しています.

Valgrind を使用してメモリリークとアクセスエラーをチェックする

GDAL ユニットテストスイートは,Valgrind ツールを使用してメモリエラー (リークや不正な読み取り/書き込みなど) を検出するために実行できます.

テストスイートは Valgrind の下でかなり遅くなります (おそらく 10 倍程度),したがって,通常は上記の方法でテストのサブセットを実行することが望ましいです.

警告

valgrind ctest を呼び出しても,テストは valgrind の下で実行されません.Valgrind を ctest と組み合わせて使用することは可能ですが,``pytest`` または gdal_unit_test を直接呼び出す方が簡単です.

Valgrind からの多数の false-positive エラーを回避するためには,次の準備手順が必要です:

  1. 多くの false-positive エラーは Python 自体によって生成されます.これらのほとんどは,テストを実行するために使用される Python インタプリタのバージョンに対応する抑制ファイルを取得することで削除できます.このファイルは,Python のソースディストリビューションにあるか,直接 GitHub からダウンロードできます (たとえば, https://raw.githubusercontent.com/python/cpython/3.11/Misc/valgrind-python.supp)

  2. GDAL テストスイートまたはそれが使用するライブラリ (SWIG, numpy など) によっていくつかの false-positive エラーが生成されます.これらは,``autotest/valgrind-gdal.supp`` ファイルで GDAL リポジトリから削除できます.

  3. Python ユニットテストを実行する場合,Python の内部メモリアロケータの代わりにデフォルトのシステムメモリアロケータを使用する必要があります.これは,``PYTHONMALLOC`` 環境変数を malloc に設定することで行うことができます.

  4. Python ユニットテストを実行する場合,Valgrind は現在抑制できない多数の "Invalid file descriptor" 警告を報告します.これらは,``grep -v "invalid file descriptor|alternative log fd"`` を使用して出力から削除できます.

以下を組み合わせることで,次のように Python テストのサブセットのために valgrind を実行できます:

PYTHONMALLOC=malloc valgrind \
    --leak-check=full \
    --suppressions=/home/dan/dev/valgrind-python.supp \
    --suppressions=/home/dan/dev/gdal/autotest/valgrind-gdal.supp \
    pytest -v autotest/utilities 2>&1 | (grep -v "invalid file descriptor\|alternative log fd" || true) | tee valgrind-out

注釈

上記のような冗長なコマンドを避けるためには,抑制ファイルやその他の一般的な引数を ~/.valgrindrc ファイルに記述すると便利です.

新しいテストの書き方に関する推奨事項

可能な限り Python ベースのテストを使用することが推奨されます. Python では生産性が高く,関連するコンパイル時間がないため (コンパイル時間は継続的統合から受け取るフィードバックに影響します).

C/C++ ベースのテストは,SWIG Python バインディングを使用してテストできない C++ 固有の側面に予約するべきです.たとえば,C++ 演算子 (コピー/ムーブコンストラクタ/代入演算子,イテレータインタフェースなど) のテストや,SWIG にマッピングされていない C/C++ 機能 (たとえば,CPL ユーティリティ関数/クラス) のテストです.

Python テスト

Python テストは, RFC 72: Update autotest suite to use pytest 以降,`pytest <https://docs.pytest.org/en/latest/>`__ フレームワークを使用しています.

テストケースは,他のテストケースと独立しているため,潜在的に分離された方法で実行されるか,他のテストケースと並行して実行されるように書かれるべきです.特に一時ファイルは,他のテストと競合しない名前で作成する必要があります:可能であれば,pytest の tmp_path フィクスチャを使用してください. <https://docs.pytest.org/en/7.1.x/how-to/tmp_path.html#the-tmp-path-fixture>`__.

オプションのドライバが存在する必要があるテストケースには,``@pytest.mark.require_driver(driver_name)`` をアノテーションとして使用します.

特定のドライバが利用可能である必要があるテストファイルの最初に pytestmark = pytest.mark.require_driver("driver_name") を使用します.これは,通常,特定のドライバのテストを書くときに使用します.

特別な前提条件が必要であるため,通常はデフォルトで実行されないテストを示すために,``@pytest.mark.require_run_on_demand`` をアノテーションとして使用します.たとえば,多くの RAM を使用するなど,継続的統合によって自動的に実行されるのに適していない場合です.

変化をテストするテスト関数のアノテーションとして,``@pytest.mark.parametrize(...)`` を使用します.詳細は,https://docs.pytest.org/en/latest/parametrize.html にあります.

e.g.:

@pytest.mark.parametrize("dt,expected_size", [(gdal.GDT_Byte, 1),
                                              (gdal.GDT_UInt16, 2)]
def test_datatypesize(dt,expected_size):
    assert gdal.GetDataTypeSizeBytes(dt) == expected_size

代わりに

def test_datatypesize_DO_NOT_DO_THAT():
    for dt, expected_size in [(gdal.GDT_Byte, 1), (gdal.GDT_UInt16, 2)]:
        assert gdal.GetDataTypeSizeBytes(dt) == expected_size

Fixtures を使用して,テストケース間でセットアップとティアダウンのコードを共有できます.

たとえば,テストケースが実行される前に指定されたドライバを登録解除し,その後に再登録するためのテストファイルのすべてのテストケースに自動的にロードされるフィクスチャ:

@pytest.fixture(scope="module", autouse=True)
def without_filegdb_driver():
# remove FileGDB driver before running tests
filegdb_driver = ogr.GetDriverByName("FileGDB")
if filegdb_driver is not None:
    filegdb_driver.Deregister()

yield

if filegdb_driver is not None:
    print("Reregistering FileGDB driver")
    filegdb_driver.Register()

または,ドライバにオプションの機能があるかどうかを発見するための事前チェックを実行し,機能がない場合はテストケースをスキップするフィクスチャ:

@pytest.fixture()
def require_auto_load_extension():
if ogr.GetDriverByName("SQLite") is None:
    pytest.skip()

ds = ogr.Open(":memory:")
with gdaltest.error_handler():
    sql_lyr = ds.ExecuteSQL("PRAGMA compile_options")
if sql_lyr:
    for f in sql_lyr:
        if f.GetField(0) == "OMIT_LOAD_EXTENSION":
            ds.ReleaseResultSet(sql_lyr)
            pytest.skip("SQLite3 built with OMIT_LOAD_EXTENSION")
    ds.ReleaseResultSet(sql_lyr)

def test_ogr_virtualogr_1(require_auto_load_extension):
    # Invalid syntax
    assert not ogr_virtualogr_run_sql("CREATE VIRTUAL TABLE poly USING VirtualOGR()")

C++ テスト

GDAL C++ テストは, RFC 88: Use GoogleTest framework for C/C++ unit tests 以降,`GoogleTest <https://github.com/google/googletest>`__ フレームワークを使用しています.

一般的な失敗しないアサーションは,``EXPECT_TRUE(cond)``, EXPECT_FALSE(cond), EXPECT_EQ(a, b), EXPECT_NE(a, b), EXPECT_STREQ(a, b), EXPECT_LE(a, b), EXPECT_LT(a, b), EXPECT_GE(a, b), EXPECT_GT(a, b), EXPECT_NEAR(a, b, tolerance) です.これらのアサーションのいずれかが失敗すると,残りのテストケースの実行が続行されるため,通常は,ポインタを NULL に対してテストし,その後無条件にデリファレンスする場合には使用しないでください."早期にテストケースを終了する場合には,ASSERT_xxxx ファミリのアサーションを使用する必要があります.

GoogleTest は,パラメータ化されたテストの機能も提供しています.たとえば:

class DataTypeTupleFixture:
        public test_gdal,
        public ::testing::WithParamInterface<std::tuple<GDALDataType, GDALDataType>>
{
public:
    static std::vector<std::tuple<GDALDataType, GDALDataType>> GetTupleValues()
    {
        std::vector<std::tuple<GDALDataType, GDALDataType>> ret;
        for( GDALDataType eIn = GDT_Byte; eIn < GDT_TypeCount; eIn = static_cast<GDALDataType>(eIn + 1) )
        {
            for( GDALDataType eOut = GDT_Byte; eOut < GDT_TypeCount; eOut = static_cast<GDALDataType>(eOut + 1) )
            {
                ret.emplace_back(std::make_tuple(eIn, eOut));
            }
        }
        return ret;
    }
};

// Test GDALDataTypeUnion() on all (GDALDataType, GDALDataType) combinations
TEST_P(DataTypeTupleFixture, GDALDataTypeUnion_generic)
{
    GDALDataType eDT1 = std::get<0>(GetParam());
    GDALDataType eDT2 = std::get<1>(GetParam());
    GDALDataType eDT = GDALDataTypeUnion(eDT1,eDT2 );
    EXPECT_EQ( eDT, GDALDataTypeUnion(eDT2,eDT1) );
    EXPECT_GE( GDALGetDataTypeSize(eDT), GDALGetDataTypeSize(eDT1) );
    EXPECT_GE( GDALGetDataTypeSize(eDT), GDALGetDataTypeSize(eDT2) );
    EXPECT_TRUE( (GDALDataTypeIsComplex(eDT) && (GDALDataTypeIsComplex(eDT1) || GDALDataTypeIsComplex(eDT2))) ||
            (!GDALDataTypeIsComplex(eDT) && !GDALDataTypeIsComplex(eDT1) && !GDALDataTypeIsComplex(eDT2)) );

    EXPECT_TRUE( !(GDALDataTypeIsFloating(eDT1) || GDALDataTypeIsFloating(eDT2)) || GDALDataTypeIsFloating(eDT));
    EXPECT_TRUE( !(GDALDataTypeIsSigned(eDT1) || GDALDataTypeIsSigned(eDT2)) || GDALDataTypeIsSigned(eDT));
}

INSTANTIATE_TEST_SUITE_P(
        test_gdal,
        DataTypeTupleFixture,
        ::testing::ValuesIn(DataTypeTupleFixture::GetTupleValues()),
        [](const ::testing::TestParamInfo<DataTypeTupleFixture::ParamType>& l_info) {
            GDALDataType eDT1 = std::get<0>(l_info.param);
            GDALDataType eDT2 = std::get<1>(l_info.param);
            return std::string(GDALGetDataTypeName(eDT1)) + "_" + GDALGetDataTypeName(eDT2);
        }
);

テストカバレッジレポート

GDAL 継続的統合には,``gcov`` GCC モジュールを使用して Python および C++ autotest テストの行カバレッジを取得する coverage 構成があります.

これは,`Coveralls GitHub Action <https://github.com/marketplace/actions/coveralls-github-action>`__ によって使用され,https://coveralls.io/github/OSGeo/gdal に結果をアップロードします.プッシュとプルリクエストイベントの両方です.

lcov によって生成された最新の master ビルドの行カバレッジ結果のやや見栄えの良い出力も,https://gdalautotest-coverage-results.github.io/coverage_html/index.html で利用できます.

コミット後のテスト

週次の静的解析は,`Coverity <https://scan.coverity.com>`__ によって実行されます.開発者/メンテナーは,`GDAL プロジェクトページ <https://scan.coverity.com/projects/749>`__ でアクセスをリクエストできます.