blob: 311491c5d7ed258e4e93f6aa9343db7878f7fe8e [file]
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""
DataFrame API golden file tests.
Analogous to ``SQLQueryTestSuite`` but tests are written in native Python using
the PySpark DataFrame API. Each ``.test`` file in this directory describes a
list of test cases; every case points to a standalone script under
``scripts/`` that builds the DataFrame under test. The expected outputs live
inline in the ``.test`` file, which doubles as the golden file. See
``pyspark.sql.tests.df_golden.df_golden`` for the file format.
Running the tests::
python/run-tests --testnames pyspark.sql.tests.df_golden.test_df_golden
Regenerating golden files
-------------------------
Set ``SPARK_GENERATE_GOLDEN_FILES=1`` before running the tests, or use the
wrapper script::
python/pyspark/sql/tests/df_golden/regenerate.sh [--verify]
With ``--verify`` the wrapper re-runs the tests afterwards against the
regenerated files.
Adding a new test file
----------------------
Drop a new ``<name>.test`` file in this directory and its case scripts under
``scripts/<name>/`` -- the ``.test`` file is auto-discovered (a
``test_<name>`` method is registered here), no code changes needed. Then
regenerate the golden files with the steps above.
"""
import os
from pyspark.testing.connectutils import ReusedConnectTestCase
from pyspark.sql.tests.df_golden.df_golden import run_golden_test
_THIS_DIR = os.path.dirname(os.path.abspath(__file__))
class DFGoldenTestBase(ReusedConnectTestCase):
"""
Base class for DataFrame golden file tests.
Subclasses just need to exist -- test methods are auto-registered from
the ``.test`` files found in ``test_file_dir``.
Each ``.test`` file runs in its own Spark Connect session (a
``spark.newSession()`` call against the shared local Connect server that
:class:`ReusedConnectTestCase` starts in ``setUpClass``), which is the
Connect counterpart of ``SQLQueryTestSuite``'s per-file
``spark.newSession()``. State created by a file's case scripts -- temp
views, UDFs, session confs -- is discarded with the session and cannot
leak into other files.
"""
test_file_dir = _THIS_DIR
def _run_golden_test(self, filename):
session = self.spark.newSession()
try:
run_golden_test(self, session, os.path.join(self.test_file_dir, filename))
finally:
# Release only this sub-session server-side and close its client
# channel. We must NOT call ``session.stop()``: under
# ``SPARK_LOCAL_REMOTE`` (the test harness) ``stop()`` terminates the
# shared local Connect server, breaking the rest of the suite and
# hanging ``tearDownClass``'s ``spark.stop()`` in release retries
# against the dead server until the test times out.
client = session.client
try:
client.release_session()
except Exception:
pass
try:
client.close()
except Exception:
pass
def _register_tests(cls):
"""
Discover ``.test`` files and create a test method for each one on *cls*.
Finding no ``.test`` files registers a failing test instead of zero
tests: a packaging problem that drops the data files must fail the
suite, not silently turn it into a green no-op.
"""
test_files = []
if os.path.isdir(cls.test_file_dir):
test_files = sorted(f for f in os.listdir(cls.test_file_dir) if f.endswith(".test"))
if not test_files:
def test_discovery_failed(self):
self.fail("no .test files found in {}".format(cls.test_file_dir))
cls.test_discovery_failed = test_discovery_failed
return
for filename in test_files:
def _make_test(f=filename):
def test_method(self):
self._run_golden_test(f)
return test_method
name = filename[: -len(".test")]
setattr(cls, "test_{}".format(name), _make_test())
class DFGoldenTest(DFGoldenTestBase):
"""DataFrame golden file tests for all ``.test`` files in this directory."""
pass
_register_tests(DFGoldenTest)
if __name__ == "__main__":
from pyspark.testing import main
main()