Squashed 'libraries/action-scheduler/' content from commit a95f351
git-subtree-dir: libraries/action-scheduler git-subtree-split: a95f351058eada5e5281faa22e5a40865542e839
This commit is contained in:
70
tests/ActionScheduler_UnitTestCase.php
Normal file
70
tests/ActionScheduler_UnitTestCase.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
// phpcs:disable WordPress.DateTime.RestrictedFunctions.timezone_change_date_default_timezone_set
|
||||
|
||||
/**
|
||||
* Class ActionScheduler_UnitTestCase
|
||||
*/
|
||||
class ActionScheduler_UnitTestCase extends WP_UnitTestCase {
|
||||
|
||||
/**
|
||||
* Timezone string.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $existing_timezone;
|
||||
|
||||
/**
|
||||
* Perform test set-up work.
|
||||
*/
|
||||
public function set_up() {
|
||||
ActionScheduler_Callbacks::add_callbacks();
|
||||
parent::set_up();
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform test tear-down work.
|
||||
*/
|
||||
public function tear_down() {
|
||||
ActionScheduler_Callbacks::remove_callbacks();
|
||||
parent::tear_down();
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts the number of test cases executed by run(TestResult result).
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function count(): int {
|
||||
return ( 'UTC' === date_default_timezone_get() ) ? 2 : 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* We want to run every test multiple times using a different timezone to make sure
|
||||
* that they are unaffected by changes to PHP's timezone.
|
||||
*
|
||||
* @param null|\PHPUnit\Framework\TestResult $result Test result.
|
||||
*/
|
||||
public function run( ?PHPUnit\Framework\TestResult $result = null ): \PHPUnit\Framework\TestResult {
|
||||
|
||||
if ( is_null( $result ) ) {
|
||||
$result = $this->createResult();
|
||||
}
|
||||
|
||||
$this->existing_timezone = date_default_timezone_get();
|
||||
|
||||
if ( 'UTC' !== $this->existing_timezone ) {
|
||||
date_default_timezone_set( 'UTC' );
|
||||
$result->run( $this );
|
||||
}
|
||||
|
||||
date_default_timezone_set( 'Pacific/Fiji' ); // UTC+12.
|
||||
$result->run( $this );
|
||||
|
||||
date_default_timezone_set( 'Pacific/Tahiti' ); // UTC-10: it's a magical place.
|
||||
$result->run( $this );
|
||||
|
||||
date_default_timezone_set( $this->existing_timezone );
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
20
tests/README.md
Normal file
20
tests/README.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# Action Scheduler tests
|
||||
|
||||
To run unit tests:
|
||||
|
||||
1. Make sure that PHPUnit is installed by running:
|
||||
```
|
||||
$ composer install
|
||||
```
|
||||
|
||||
2. Install WordPress and the WP Unit Test lib using the `install.sh` script:
|
||||
```
|
||||
$ tests/bin/install.sh <db-name> <db-user> <db-password> [db-host] [wp-version] [skip-database-creation]
|
||||
```
|
||||
|
||||
You may need to quote strings with backslashes to prevent them from being processed by the shell or other programs.
|
||||
|
||||
Then, to run the tests:
|
||||
```
|
||||
$ composer run test
|
||||
```
|
||||
181
tests/bin/install.sh
Executable file
181
tests/bin/install.sh
Executable file
@@ -0,0 +1,181 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
if [ $# -lt 3 ]; then
|
||||
echo "usage: $0 <db-name> <db-user> <db-pass> [db-host] [wp-version] [skip-database-creation]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
DB_NAME=$1
|
||||
DB_USER=$2
|
||||
DB_PASS=$3
|
||||
DB_HOST=${4-localhost}
|
||||
WP_VERSION=${5-latest}
|
||||
SKIP_DB_CREATE=${6-false}
|
||||
|
||||
TMPDIR=${TMPDIR-/tmp}
|
||||
TMPDIR=$(echo $TMPDIR | sed -e "s/\/$//")
|
||||
WP_TESTS_DIR=${WP_TESTS_DIR-$TMPDIR/wordpress-tests-lib}
|
||||
WP_CORE_DIR=${WP_CORE_DIR-$TMPDIR/wordpress}
|
||||
|
||||
download() {
|
||||
if [ `which curl` ]; then
|
||||
curl -s "$1" > "$2";
|
||||
elif [ `which wget` ]; then
|
||||
wget -nv -O "$2" "$1"
|
||||
fi
|
||||
}
|
||||
|
||||
if [[ $WP_VERSION =~ ^[0-9]+\.[0-9]+\-(beta|RC)[0-9]+$ ]]; then
|
||||
WP_BRANCH=${WP_VERSION%\-*}
|
||||
WP_TESTS_TAG="branches/$WP_BRANCH"
|
||||
|
||||
elif [[ $WP_VERSION =~ ^[0-9]+\.[0-9]+$ ]]; then
|
||||
WP_TESTS_TAG="branches/$WP_VERSION"
|
||||
elif [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0-9]+ ]]; then
|
||||
if [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0] ]]; then
|
||||
# version x.x.0 means the first release of the major version, so strip off the .0 and download version x.x
|
||||
WP_TESTS_TAG="tags/${WP_VERSION%??}"
|
||||
else
|
||||
WP_TESTS_TAG="tags/$WP_VERSION"
|
||||
fi
|
||||
elif [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then
|
||||
WP_TESTS_TAG="trunk"
|
||||
else
|
||||
# http serves a single offer, whereas https serves multiple. we only want one
|
||||
download http://api.wordpress.org/core/version-check/1.7/ /tmp/wp-latest.json
|
||||
grep '[0-9]+\.[0-9]+(\.[0-9]+)?' /tmp/wp-latest.json
|
||||
LATEST_VERSION=$(grep -o '"version":"[^"]*' /tmp/wp-latest.json | sed 's/"version":"//')
|
||||
if [[ -z "$LATEST_VERSION" ]]; then
|
||||
echo "Latest WordPress version could not be found"
|
||||
exit 1
|
||||
fi
|
||||
WP_TESTS_TAG="tags/$LATEST_VERSION"
|
||||
fi
|
||||
set -ex
|
||||
|
||||
install_wp() {
|
||||
|
||||
if [ -d $WP_CORE_DIR ]; then
|
||||
return;
|
||||
fi
|
||||
|
||||
mkdir -p $WP_CORE_DIR
|
||||
|
||||
if [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then
|
||||
mkdir -p $TMPDIR/wordpress-trunk
|
||||
rm -rf $TMPDIR/wordpress-trunk/*
|
||||
svn export --quiet https://core.svn.wordpress.org/trunk $TMPDIR/wordpress-trunk/wordpress
|
||||
mv $TMPDIR/wordpress-trunk/wordpress/* $WP_CORE_DIR
|
||||
else
|
||||
if [ $WP_VERSION == 'latest' ]; then
|
||||
local ARCHIVE_NAME='latest'
|
||||
elif [[ $WP_VERSION =~ [0-9]+\.[0-9]+ ]]; then
|
||||
# https serves multiple offers, whereas http serves single.
|
||||
download https://api.wordpress.org/core/version-check/1.7/ $TMPDIR/wp-latest.json
|
||||
if [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0] ]]; then
|
||||
# version x.x.0 means the first release of the major version, so strip off the .0 and download version x.x
|
||||
LATEST_VERSION=${WP_VERSION%??}
|
||||
else
|
||||
# otherwise, scan the releases and get the most up to date minor version of the major release
|
||||
local VERSION_ESCAPED=`echo $WP_VERSION | sed 's/\./\\\\./g'`
|
||||
LATEST_VERSION=$(grep -o '"version":"'$VERSION_ESCAPED'[^"]*' $TMPDIR/wp-latest.json | sed 's/"version":"//' | head -1)
|
||||
fi
|
||||
if [[ -z "$LATEST_VERSION" ]]; then
|
||||
local ARCHIVE_NAME="wordpress-$WP_VERSION"
|
||||
else
|
||||
local ARCHIVE_NAME="wordpress-$LATEST_VERSION"
|
||||
fi
|
||||
else
|
||||
local ARCHIVE_NAME="wordpress-$WP_VERSION"
|
||||
fi
|
||||
download https://wordpress.org/${ARCHIVE_NAME}.tar.gz $TMPDIR/wordpress.tar.gz
|
||||
tar --strip-components=1 -zxmf $TMPDIR/wordpress.tar.gz -C $WP_CORE_DIR
|
||||
fi
|
||||
|
||||
download https://raw.github.com/markoheijnen/wp-mysqli/master/db.php $WP_CORE_DIR/wp-content/db.php
|
||||
}
|
||||
|
||||
install_test_suite() {
|
||||
# portable in-place argument for both GNU sed and Mac OSX sed
|
||||
if [[ $(uname -s) == 'Darwin' ]]; then
|
||||
local ioption='-i.bak'
|
||||
else
|
||||
local ioption='-i'
|
||||
fi
|
||||
|
||||
# set up testing suite if it doesn't yet exist
|
||||
if [ ! -d $WP_TESTS_DIR ]; then
|
||||
# set up testing suite
|
||||
mkdir -p $WP_TESTS_DIR
|
||||
rm -rf $WP_TESTS_DIR/{includes,data}
|
||||
svn export --quiet --ignore-externals https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/includes/ $WP_TESTS_DIR/includes
|
||||
svn export --quiet --ignore-externals https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/data/ $WP_TESTS_DIR/data
|
||||
fi
|
||||
|
||||
if [ ! -f wp-tests-config.php ]; then
|
||||
download https://develop.svn.wordpress.org/${WP_TESTS_TAG}/wp-tests-config-sample.php "$WP_TESTS_DIR"/wp-tests-config.php
|
||||
# remove all forward slashes in the end
|
||||
WP_CORE_DIR=$(echo $WP_CORE_DIR | sed "s:/\+$::")
|
||||
sed $ioption "s:dirname( __FILE__ ) . '/src/':'$WP_CORE_DIR/':" "$WP_TESTS_DIR"/wp-tests-config.php
|
||||
sed $ioption "s:__DIR__ . '/src/':'$WP_CORE_DIR/':" "$WP_TESTS_DIR"/wp-tests-config.php
|
||||
sed $ioption "s/youremptytestdbnamehere/$DB_NAME/" "$WP_TESTS_DIR"/wp-tests-config.php
|
||||
sed $ioption "s/yourusernamehere/$DB_USER/" "$WP_TESTS_DIR"/wp-tests-config.php
|
||||
sed $ioption "s/yourpasswordhere/$DB_PASS/" "$WP_TESTS_DIR"/wp-tests-config.php
|
||||
sed $ioption "s|localhost|${DB_HOST}|" "$WP_TESTS_DIR"/wp-tests-config.php
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
recreate_db() {
|
||||
shopt -s nocasematch
|
||||
if [[ $1 =~ ^(y|yes)$ ]]
|
||||
then
|
||||
mysqladmin drop $DB_NAME -f --user="$DB_USER" --password="$DB_PASS"$EXTRA
|
||||
create_db
|
||||
echo "Recreated the database ($DB_NAME)."
|
||||
else
|
||||
echo "Leaving the existing database ($DB_NAME) in place."
|
||||
fi
|
||||
shopt -u nocasematch
|
||||
}
|
||||
|
||||
create_db() {
|
||||
mysqladmin create $DB_NAME --user="$DB_USER" --password="$DB_PASS"$EXTRA
|
||||
}
|
||||
|
||||
install_db() {
|
||||
|
||||
if [ ${SKIP_DB_CREATE} = "true" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# parse DB_HOST for port or socket references
|
||||
local PARTS=(${DB_HOST//\:/ })
|
||||
local DB_HOSTNAME=${PARTS[0]};
|
||||
local DB_SOCK_OR_PORT=${PARTS[1]};
|
||||
local EXTRA=""
|
||||
|
||||
if ! [ -z $DB_HOSTNAME ] ; then
|
||||
if [ $(echo $DB_SOCK_OR_PORT | grep -e '^[0-9]\{1,\}$') ]; then
|
||||
EXTRA=" --host=$DB_HOSTNAME --port=$DB_SOCK_OR_PORT --protocol=tcp"
|
||||
elif ! [ -z $DB_SOCK_OR_PORT ] ; then
|
||||
EXTRA=" --socket=$DB_SOCK_OR_PORT"
|
||||
elif ! [ -z $DB_HOSTNAME ] ; then
|
||||
EXTRA=" --host=$DB_HOSTNAME --protocol=tcp"
|
||||
fi
|
||||
fi
|
||||
|
||||
# create database
|
||||
if [ $(mysql --user="$DB_USER" --password="$DB_PASS"$EXTRA --execute='show databases;' | grep ^$DB_NAME$) ]
|
||||
then
|
||||
echo "Reinstalling will delete the existing test database ($DB_NAME)"
|
||||
read -p 'Are you sure you want to proceed? [y/N]: ' DELETE_EXISTING_DB
|
||||
recreate_db $DELETE_EXISTING_DB
|
||||
else
|
||||
create_db
|
||||
fi
|
||||
}
|
||||
|
||||
install_wp
|
||||
install_test_suite
|
||||
install_db
|
||||
37
tests/bootstrap.php
Normal file
37
tests/bootstrap.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
// phpcs:disable PEAR.Files.IncludingFile.UseRequireOnce
|
||||
|
||||
$GLOBALS['wp_tests_options']['template'] = 'twentyseventeen';
|
||||
$GLOBALS['wp_tests_options']['stylesheet'] = 'twentyseventeen';
|
||||
$GLOBALS['wp_tests_options']['active_plugins'][] = basename( dirname( __DIR__ ) ) . '/action-scheduler.php';
|
||||
|
||||
// Check for select constants defined as environment variables.
|
||||
foreach ( array( 'WP_CONTENT_DIR', 'WP_CONTENT_URL', 'WP_PLUGIN_DIR', 'WP_PLUGIN_URL', 'WPMU_PLUGIN_DIR' ) as $env_constant ) {
|
||||
if ( false !== getenv( $env_constant ) && ! defined( $env_constant ) ) {
|
||||
define( $env_constant, getenv( $env_constant ) );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! defined( 'WP_PLUGIN_DIR' ) ) {
|
||||
define( 'WP_PLUGIN_DIR', dirname( dirname( dirname( __FILE__ ) ) ) );
|
||||
}
|
||||
|
||||
// PHPUnit polyfills as required by the core WP test framework.
|
||||
require_once __DIR__ . '/../vendor/yoast/phpunit-polyfills/phpunitpolyfills-autoload.php';
|
||||
|
||||
$wordpress_tests_dir = getenv( 'WP_TESTS_DIR' ) ? getenv( 'WP_TESTS_DIR' ) : sys_get_temp_dir() . '/wordpress-tests-lib';
|
||||
require_once $wordpress_tests_dir . '/includes/functions.php';
|
||||
require $wordpress_tests_dir . '/includes/bootstrap.php';
|
||||
|
||||
require_once dirname( dirname( __FILE__ ) ) . '/action-scheduler.php';
|
||||
|
||||
if ( class_exists( 'PHPUnit\Framework\TestResult' ) ) { // PHPUnit 6.0 or newer.
|
||||
include_once 'ActionScheduler_UnitTestCase.php';
|
||||
} else {
|
||||
include_once 'phpunit/deprecated/ActionScheduler_UnitTestCase.php';
|
||||
}
|
||||
|
||||
include_once 'phpunit/helpers/ActionScheduler_Callbacks.php';
|
||||
include_once 'phpunit/ActionScheduler_Mocker.php';
|
||||
include_once 'phpunit/ActionScheduler_Mock_Async_Request_QueueRunner.php';
|
||||
include_once 'phpunit/jobstore/AbstractStoreTest.php';
|
||||
49
tests/phpunit.xml.dist
Normal file
49
tests/phpunit.xml.dist
Normal file
@@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<phpunit backupGlobals="false"
|
||||
backupStaticAttributes="false"
|
||||
colors="true"
|
||||
convertErrorsToExceptions="true"
|
||||
convertNoticesToExceptions="true"
|
||||
convertWarningsToExceptions="true"
|
||||
processIsolation="false"
|
||||
stopOnFailure="false"
|
||||
bootstrap="bootstrap.php"
|
||||
>
|
||||
<testsuites>
|
||||
<testsuite name="Migration">
|
||||
<directory phpVersion="5.6">./phpunit/migration</directory>
|
||||
</testsuite>
|
||||
<testsuite name="Tables">
|
||||
<file phpVersion="5.6">./phpunit/jobstore/ActionScheduler_DBStoreMigrator_Test.php</file>
|
||||
<file phpVersion="5.6">./phpunit/jobstore/ActionScheduler_DBStore_Test.php</file>
|
||||
<file phpVersion="5.6">./phpunit/jobstore/ActionScheduler_HybridStore_Test.php</file>
|
||||
<file phpVersion="5.6">./phpunit/logging/ActionScheduler_DBLogger_Test.php</file>
|
||||
</testsuite>
|
||||
<testsuite name="Action Scheduler">
|
||||
<directory>./phpunit/helpers</directory>
|
||||
<directory>./phpunit/jobs</directory>
|
||||
<directory>./phpunit/lock</directory>
|
||||
<directory>./phpunit/procedural_api</directory>
|
||||
<directory>./phpunit/runner</directory>
|
||||
<directory>./phpunit/schedules</directory>
|
||||
<directory>./phpunit/versioning</directory>
|
||||
<file>./phpunit/logging/ActionScheduler_wpCommentLogger_Test.php</file>
|
||||
<file>./phpunit/jobstore/ActionScheduler_wpPostStore_Test.php</file>
|
||||
<file>./phpunit/jobstore/ActionScheduler_RecurringActionScheduler_Test.php</file>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<groups>
|
||||
<exclude>
|
||||
<group>ignore</group>
|
||||
</exclude>
|
||||
</groups>
|
||||
<filter>
|
||||
<whitelist>
|
||||
<directory suffix=".php">..</directory>
|
||||
<exclude>
|
||||
<directory>.</directory>
|
||||
</exclude>
|
||||
</whitelist>
|
||||
</filter>
|
||||
</phpunit>
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* ActionScheduler_Mock_AsyncRequest_QueueRunner class.
|
||||
*/
|
||||
class ActionScheduler_Mock_AsyncRequest_QueueRunner extends ActionScheduler_AsyncRequest_QueueRunner {
|
||||
|
||||
/**
|
||||
* Do not run queues via async requests.
|
||||
*/
|
||||
protected function allow() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
32
tests/phpunit/ActionScheduler_Mocker.php
Normal file
32
tests/phpunit/ActionScheduler_Mocker.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* ActionScheduler_Mocker class.
|
||||
*/
|
||||
class ActionScheduler_Mocker {
|
||||
|
||||
/**
|
||||
* Do not run queues via async requests.
|
||||
*
|
||||
* @param null|ActionScheduler_Store $store Store instance.
|
||||
*/
|
||||
public static function get_queue_runner( ?ActionScheduler_Store $store = null ) {
|
||||
|
||||
if ( ! $store ) {
|
||||
$store = ActionScheduler_Store::instance();
|
||||
}
|
||||
|
||||
return new ActionScheduler_QueueRunner( $store, null, null, self::get_async_request_queue_runner( $store ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an instance of the mock queue runner
|
||||
*
|
||||
* @param ActionScheduler_Store $store Store instance.
|
||||
*/
|
||||
protected static function get_async_request_queue_runner( ActionScheduler_Store $store ) {
|
||||
return new ActionScheduler_Mock_AsyncRequest_QueueRunner( $store );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Test suite for the ActionScheduler_RecurringActionScheduler class.
|
||||
*/
|
||||
class ActionScheduler_RecurringActionScheduler_Test extends ActionScheduler_UnitTestCase {
|
||||
|
||||
public function tear_down() {
|
||||
delete_transient( 'as_is_ensure_recurring_actions_scheduled' );
|
||||
as_unschedule_action( 'action_scheduler_ensure_recurring_actions' );
|
||||
|
||||
parent::tear_down();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the init method hooks into 'action_scheduler_init' correctly.
|
||||
*/
|
||||
public function test_init_hooks_into_action_scheduler_init() {
|
||||
global $current_screen;
|
||||
|
||||
$scheduler = new ActionScheduler_RecurringActionScheduler();
|
||||
$scheduler->init();
|
||||
|
||||
// Only apply hooks when in the admin.
|
||||
$_current_screen = $current_screen;
|
||||
set_current_screen( 'dashboard' );
|
||||
|
||||
try {
|
||||
// Verify that the 'action_scheduler_init' hook is registered with the correct callback
|
||||
$this->assertTrue(
|
||||
has_action( 'action_scheduler_init', array(
|
||||
ActionScheduler_RecurringActionScheduler::class,
|
||||
'schedule_recurring_scheduler_hook'
|
||||
) ) > 0,
|
||||
'The schedule_recurring_scheduler_hook method should be hooked into action_scheduler_init.'
|
||||
);
|
||||
} finally {
|
||||
// Clean up to avoid affecting any other tests.
|
||||
$current_screen = $_current_screen;
|
||||
remove_action(
|
||||
'action_scheduler_init',
|
||||
array(
|
||||
ActionScheduler_RecurringActionScheduler::class,
|
||||
'schedule_recurring_scheduler_hook'
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that schedule_recurring_scheduler_hook schedules the recurring action when not already scheduled.
|
||||
*/
|
||||
public function test_schedule_recurring_scheduler_hook_schedules_action() {
|
||||
// Ensure no action is scheduled initially
|
||||
$this->assertFalse(
|
||||
as_has_scheduled_action( 'action_scheduler_ensure_recurring_actions' ),
|
||||
'No recurring action should be scheduled initially.'
|
||||
);
|
||||
|
||||
$scheduler = new ActionScheduler_RecurringActionScheduler();
|
||||
$scheduler->schedule_recurring_scheduler_hook();
|
||||
|
||||
$this->assertTrue(
|
||||
as_has_scheduled_action( 'action_scheduler_ensure_recurring_actions' ),
|
||||
'The recurring action should now be scheduled.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that schedule_recurring_scheduler_hook respects caching and does not schedule actions redundantly.
|
||||
*/
|
||||
public function test_schedule_recurring_scheduler_hook__respects_cache() {
|
||||
// Ensure no action is scheduled initially
|
||||
$this->assertFalse(
|
||||
as_has_scheduled_action( 'action_scheduler_ensure_recurring_actions' ),
|
||||
'No recurring action should be scheduled initially.'
|
||||
);
|
||||
|
||||
// Simulate a transient hit
|
||||
set_transient( 'as_is_ensure_recurring_actions_scheduled', true, HOUR_IN_SECONDS );
|
||||
|
||||
// Spy on as_schedule_recurring_action to verify it does NOT get called
|
||||
$scheduler = new ActionScheduler_RecurringActionScheduler();
|
||||
$scheduler->schedule_recurring_scheduler_hook();
|
||||
|
||||
// Assert that no new action was scheduled due to transient hit
|
||||
$this->assertFalse(
|
||||
as_has_scheduled_action( 'action_scheduler_ensure_recurring_actions' ),
|
||||
'No new recurring action should be scheduled due to transient hit.'
|
||||
);
|
||||
}
|
||||
}
|
||||
71
tests/phpunit/deprecated/ActionScheduler_UnitTestCase.php
Normal file
71
tests/phpunit/deprecated/ActionScheduler_UnitTestCase.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
// phpcs:disable WordPress.DateTime.RestrictedFunctions.timezone_change_date_default_timezone_set
|
||||
// phpcs:disable Generic.Classes.DuplicateClassName.Found
|
||||
|
||||
/**
|
||||
* Class ActionScheduler_UnitTestCase
|
||||
*/
|
||||
class ActionScheduler_UnitTestCase extends WP_UnitTestCase {
|
||||
|
||||
/**
|
||||
* Timezone string.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $existing_timezone;
|
||||
|
||||
/**
|
||||
* Perform test set-up work.
|
||||
*/
|
||||
public function set_up() {
|
||||
ActionScheduler_Callbacks::add_callbacks();
|
||||
parent::set_up();
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform test tear-down work.
|
||||
*/
|
||||
public function tear_down() {
|
||||
ActionScheduler_Callbacks::remove_callbacks();
|
||||
parent::tear_down();
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts the number of test cases executed by run(TestResult result).
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function count() {
|
||||
return ( 'UTC' === date_default_timezone_get() ) ? 2 : 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* We want to run every test multiple times using a different timezone to make sure
|
||||
* that they are unaffected by changes to PHP's timezone.
|
||||
*
|
||||
* @param null|PHPUnit_Framework_TestResult $result Test result.
|
||||
*/
|
||||
public function run( ?PHPUnit_Framework_TestResult $result = null ) {
|
||||
|
||||
if ( is_null( $result ) ) {
|
||||
$result = $this->createResult();
|
||||
}
|
||||
|
||||
$this->existing_timezone = date_default_timezone_get();
|
||||
|
||||
if ( 'UTC' !== $this->existing_timezone ) {
|
||||
date_default_timezone_set( 'UTC' );
|
||||
$result->run( $this );
|
||||
}
|
||||
|
||||
date_default_timezone_set( 'Pacific/Fiji' ); // UTC+12.
|
||||
$result->run( $this );
|
||||
|
||||
date_default_timezone_set( 'Pacific/Tahiti' ); // UTC-10: it's a magical place.
|
||||
$result->run( $this );
|
||||
|
||||
date_default_timezone_set( $this->existing_timezone );
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
34
tests/phpunit/helpers/ActionScheduler_Callbacks.php
Normal file
34
tests/phpunit/helpers/ActionScheduler_Callbacks.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* ActionScheduler_Callbacks class.
|
||||
*/
|
||||
class ActionScheduler_Callbacks {
|
||||
/**
|
||||
* Scheduled action hook that can be used when we want to simulate an action
|
||||
* with a registered callback.
|
||||
*/
|
||||
const HOOK_WITH_CALLBACK = 'hook_with_callback';
|
||||
|
||||
/**
|
||||
* Setup callbacks for different types of hook.
|
||||
*/
|
||||
public static function add_callbacks() {
|
||||
add_action( self::HOOK_WITH_CALLBACK, array( __CLASS__, 'empty_callback' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove callbacks.
|
||||
*/
|
||||
public static function remove_callbacks() {
|
||||
remove_action( self::HOOK_WITH_CALLBACK, array( __CLASS__, 'empty_callback' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* This stub is used as the callback function for the ActionScheduler_Callbacks::HOOK_WITH_CALLBACK hook.
|
||||
*
|
||||
* Action Scheduler will mark actions as 'failed' if a callback does not exist, this
|
||||
* simply serves to act as the callback for various test scenarios in child classes.
|
||||
*/
|
||||
public static function empty_callback() {}
|
||||
}
|
||||
48
tests/phpunit/helpers/ActionScheduler_Compatibility_Test.php
Normal file
48
tests/phpunit/helpers/ActionScheduler_Compatibility_Test.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
// phpcs:disable WordPress.PHP.IniSet.max_execution_time_Blacklisted
|
||||
|
||||
/**
|
||||
* @group helpers
|
||||
*/
|
||||
class ActionScheduler_Compatibility_Test extends ActionScheduler_UnitTestCase {
|
||||
/**
|
||||
* Test the logic relating to ActionScheduler_Compatibility::raise_time_limit().
|
||||
*/
|
||||
public function test_raise_time_limit() {
|
||||
// We'll want to restore things after this test.
|
||||
$default_max_execution_time = ini_get( 'max_execution_time' );
|
||||
|
||||
ini_set( 'max_execution_time', 0 );
|
||||
ActionScheduler_Compatibility::raise_time_limit( 10 );
|
||||
$this->assertEquals(
|
||||
'0',
|
||||
ini_get( 'max_execution_time' ),
|
||||
'If the max_execution_time was already zero (unlimited), then it will not be changed.'
|
||||
);
|
||||
|
||||
ini_set( 'max_execution_time', 60 );
|
||||
ActionScheduler_Compatibility::raise_time_limit( 30 );
|
||||
$this->assertEquals(
|
||||
'60',
|
||||
ini_get( 'max_execution_time' ),
|
||||
'If the max_execution_time was already a higher value than we specify, then it will not be changed.'
|
||||
);
|
||||
|
||||
ActionScheduler_Compatibility::raise_time_limit( 200 );
|
||||
$this->assertEquals(
|
||||
'200',
|
||||
ini_get( 'max_execution_time' ),
|
||||
'If the max_execution_time was a lower value than we specify, but was above zero, then it will be updated to the new value.'
|
||||
);
|
||||
|
||||
ActionScheduler_Compatibility::raise_time_limit( 0 );
|
||||
$this->assertEquals(
|
||||
'0',
|
||||
ini_get( 'max_execution_time' ),
|
||||
'If the max_execution_time was a positive, non-zero value and we then specify zero (unlimited) as the new value, then it will be updated.'
|
||||
);
|
||||
|
||||
// Cleanup.
|
||||
ini_set( 'max_execution_time', $default_max_execution_time );
|
||||
}
|
||||
}
|
||||
110
tests/phpunit/helpers/ActionScheduler_TimezoneHelper_Test.php
Normal file
110
tests/phpunit/helpers/ActionScheduler_TimezoneHelper_Test.php
Normal file
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @group timezone
|
||||
*/
|
||||
class ActionScheduler_TimezoneHelper_Test extends ActionScheduler_UnitTestCase {
|
||||
|
||||
/**
|
||||
* Ensure that the timezone string we expect works properly.
|
||||
*
|
||||
* @dataProvider local_timezone_provider
|
||||
*
|
||||
* @param string $timezone_string Timezone.
|
||||
*/
|
||||
public function test_local_timezone_strings( $timezone_string ) {
|
||||
$timezone_filter = function ( $tz ) use ( $timezone_string ) {
|
||||
return $timezone_string;
|
||||
};
|
||||
|
||||
add_filter( 'option_timezone_string', $timezone_filter );
|
||||
|
||||
$date = new ActionScheduler_DateTime();
|
||||
$timezone = ActionScheduler_TimezoneHelper::set_local_timezone( $date )->getTimezone();
|
||||
$this->assertInstanceOf( 'DateTimeZone', $timezone );
|
||||
$this->assertEquals( $timezone_string, $timezone->getName() );
|
||||
|
||||
remove_filter( 'option_timezone_string', $timezone_filter );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get timezone strings.
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
public function local_timezone_provider() {
|
||||
return array(
|
||||
array( 'America/New_York' ),
|
||||
array( 'Australia/Melbourne' ),
|
||||
array( 'UTC' ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that most GMT offsets don't return UTC as the timezone.
|
||||
*
|
||||
* @dataProvider local_timezone_offsets_provider
|
||||
*
|
||||
* @param string $gmt_offset GMT offset.
|
||||
*/
|
||||
public function test_local_timezone_offsets( $gmt_offset ) {
|
||||
$gmt_filter = function ( $gmt ) use ( $gmt_offset ) {
|
||||
return $gmt_offset;
|
||||
};
|
||||
|
||||
$date = new ActionScheduler_DateTime();
|
||||
|
||||
add_filter( 'option_gmt_offset', $gmt_filter );
|
||||
ActionScheduler_TimezoneHelper::set_local_timezone( $date );
|
||||
remove_filter( 'option_gmt_offset', $gmt_filter );
|
||||
|
||||
$offset_in_seconds = $gmt_offset * HOUR_IN_SECONDS;
|
||||
|
||||
$this->assertEquals( $offset_in_seconds, $date->getOffset() );
|
||||
$this->assertEquals( $offset_in_seconds, $date->getOffsetTimestamp() - $date->getTimestamp() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get timezone offsets.
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
public function local_timezone_offsets_provider() {
|
||||
return array(
|
||||
array( '-11' ),
|
||||
array( '-10.5' ),
|
||||
array( '-10' ),
|
||||
array( '-9' ),
|
||||
array( '-8' ),
|
||||
array( '-7' ),
|
||||
array( '-6' ),
|
||||
array( '-5' ),
|
||||
array( '-4.5' ),
|
||||
array( '-4' ),
|
||||
array( '-3.5' ),
|
||||
array( '-3' ),
|
||||
array( '-2' ),
|
||||
array( '-1' ),
|
||||
array( '1' ),
|
||||
array( '1.5' ),
|
||||
array( '2' ),
|
||||
array( '3' ),
|
||||
array( '4' ),
|
||||
array( '5' ),
|
||||
array( '5.5' ),
|
||||
array( '5.75' ),
|
||||
array( '6' ),
|
||||
array( '7' ),
|
||||
array( '8' ),
|
||||
array( '8.5' ),
|
||||
array( '9' ),
|
||||
array( '9.5' ),
|
||||
array( '10' ),
|
||||
array( '10.5' ),
|
||||
array( '11' ),
|
||||
array( '11.5' ),
|
||||
array( '12' ),
|
||||
array( '13' ),
|
||||
);
|
||||
}
|
||||
}
|
||||
54
tests/phpunit/jobs/ActionScheduler_Action_Test.php
Normal file
54
tests/phpunit/jobs/ActionScheduler_Action_Test.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class ActionScheduler_Action_Test
|
||||
* @group actions
|
||||
*/
|
||||
class ActionScheduler_Action_Test extends ActionScheduler_UnitTestCase {
|
||||
public function test_set_schedule() {
|
||||
$time = as_get_datetime_object();
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$action = new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array(), $schedule );
|
||||
$this->assertEquals( $schedule, $action->get_schedule() );
|
||||
}
|
||||
|
||||
public function test_null_schedule() {
|
||||
$action = new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK );
|
||||
$this->assertInstanceOf( 'ActionScheduler_NullSchedule', $action->get_schedule() );
|
||||
}
|
||||
|
||||
public function test_set_hook() {
|
||||
$action = new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK );
|
||||
$this->assertEquals( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, $action->get_hook() );
|
||||
}
|
||||
|
||||
public function test_args() {
|
||||
$action = new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK );
|
||||
$this->assertEmpty( $action->get_args() );
|
||||
|
||||
$action = new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array( 5, 10, 15 ) );
|
||||
$this->assertEqualSets( array( 5, 10, 15 ), $action->get_args() );
|
||||
}
|
||||
|
||||
public function test_set_group() {
|
||||
$action = new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array(), null, 'my_group' );
|
||||
$this->assertEquals( 'my_group', $action->get_group() );
|
||||
}
|
||||
|
||||
public function test_execute() {
|
||||
$mock = new MockAction();
|
||||
|
||||
$random = md5( wp_rand() );
|
||||
add_action( $random, array( $mock, 'action' ) );
|
||||
|
||||
$action = new ActionScheduler_Action( $random, array( $random ) );
|
||||
$action->execute();
|
||||
|
||||
remove_action( $random, array( $mock, 'action' ) );
|
||||
|
||||
$this->assertEquals( 1, $mock->get_call_count() );
|
||||
$events = $mock->get_events();
|
||||
$event = reset( $events );
|
||||
$this->assertEquals( $random, reset( $event['args'] ) );
|
||||
}
|
||||
}
|
||||
15
tests/phpunit/jobs/ActionScheduler_NullAction_Test.php
Normal file
15
tests/phpunit/jobs/ActionScheduler_NullAction_Test.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class ActionScheduler_NullAction_Test
|
||||
* @group actions
|
||||
*/
|
||||
class ActionScheduler_NullAction_Test extends ActionScheduler_UnitTestCase {
|
||||
public function test_null_action() {
|
||||
$action = new ActionScheduler_NullAction();
|
||||
|
||||
$this->assertEmpty( $action->get_hook() );
|
||||
$this->assertEmpty( $action->get_args() );
|
||||
$this->assertNull( $action->get_schedule()->get_date() );
|
||||
}
|
||||
}
|
||||
163
tests/phpunit/jobstore/AbstractStoreTest.php
Normal file
163
tests/phpunit/jobstore/AbstractStoreTest.php
Normal file
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
|
||||
namespace Action_Scheduler\Tests\DataStores;
|
||||
|
||||
use ActionScheduler_Action;
|
||||
use ActionScheduler_Callbacks;
|
||||
use ActionScheduler_IntervalSchedule;
|
||||
use ActionScheduler_Mocker;
|
||||
use ActionScheduler_SimpleSchedule;
|
||||
use ActionScheduler_Store;
|
||||
use ActionScheduler_UnitTestCase;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Abstract store test class.
|
||||
*
|
||||
* Many tests for the WP Post store or the custom tables store can be shared. This abstract class contains tests that
|
||||
* apply to both stores without having to duplicate code.
|
||||
*/
|
||||
abstract class AbstractStoreTest extends ActionScheduler_UnitTestCase {
|
||||
|
||||
/**
|
||||
* Get data store for tests.
|
||||
*
|
||||
* @return ActionScheduler_Store
|
||||
*/
|
||||
abstract protected function get_store();
|
||||
|
||||
public function test_get_status() {
|
||||
$time = as_get_datetime_object( '-10 minutes' );
|
||||
$schedule = new ActionScheduler_IntervalSchedule( $time, HOUR_IN_SECONDS );
|
||||
$action = new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array(), $schedule );
|
||||
$store = $this->get_store();
|
||||
$action_id = $store->save_action( $action );
|
||||
|
||||
$this->assertEquals( ActionScheduler_Store::STATUS_PENDING, $store->get_status( $action_id ) );
|
||||
|
||||
$store->mark_complete( $action_id );
|
||||
$this->assertEquals( ActionScheduler_Store::STATUS_COMPLETE, $store->get_status( $action_id ) );
|
||||
|
||||
$store->mark_failure( $action_id );
|
||||
$this->assertEquals( ActionScheduler_Store::STATUS_FAILED, $store->get_status( $action_id ) );
|
||||
}
|
||||
|
||||
// Start tests for \ActionScheduler_Store::query_actions().
|
||||
|
||||
// phpcs:ignore Squiz.Commenting.FunctionComment.WrongStyle
|
||||
public function test_query_actions_query_type_arg_invalid_option() {
|
||||
$this->expectException( InvalidArgumentException::class );
|
||||
$this->get_store()->query_actions( array( 'hook' => ActionScheduler_Callbacks::HOOK_WITH_CALLBACK ), 'invalid' );
|
||||
}
|
||||
|
||||
public function test_query_actions_query_type_arg_valid_options() {
|
||||
$store = $this->get_store();
|
||||
$schedule = new ActionScheduler_SimpleSchedule( as_get_datetime_object( 'tomorrow' ) );
|
||||
|
||||
$action_id_1 = $store->save_action( new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array( 1 ), $schedule ) );
|
||||
$action_id_2 = $store->save_action( new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array( 1 ), $schedule ) );
|
||||
|
||||
$this->assertEquals( array( $action_id_1, $action_id_2 ), $store->query_actions( array( 'hook' => ActionScheduler_Callbacks::HOOK_WITH_CALLBACK ) ) );
|
||||
$this->assertEquals( 2, $store->query_actions( array( 'hook' => ActionScheduler_Callbacks::HOOK_WITH_CALLBACK ), 'count' ) );
|
||||
}
|
||||
|
||||
public function test_query_actions_by_single_status() {
|
||||
$store = $this->get_store();
|
||||
$schedule = new ActionScheduler_SimpleSchedule( as_get_datetime_object( 'tomorrow' ) );
|
||||
|
||||
$this->assertEquals( 0, $store->query_actions( array( 'status' => ActionScheduler_Store::STATUS_PENDING ), 'count' ) );
|
||||
|
||||
$action_id_1 = $store->save_action( new ActionScheduler_Action( 'my_hook_1', array( 1 ), $schedule ) );
|
||||
$action_id_2 = $store->save_action( new ActionScheduler_Action( 'my_hook_2', array( 1 ), $schedule ) );
|
||||
$action_id_3 = $store->save_action( new ActionScheduler_Action( 'my_hook_3', array( 1 ), $schedule ) );
|
||||
$store->mark_complete( $action_id_3 );
|
||||
|
||||
$this->assertEquals( 2, $store->query_actions( array( 'status' => ActionScheduler_Store::STATUS_PENDING ), 'count' ) );
|
||||
$this->assertEquals( 1, $store->query_actions( array( 'status' => ActionScheduler_Store::STATUS_COMPLETE ), 'count' ) );
|
||||
}
|
||||
|
||||
public function test_query_actions_by_array_status() {
|
||||
$store = $this->get_store();
|
||||
$schedule = new ActionScheduler_SimpleSchedule( as_get_datetime_object( 'tomorrow' ) );
|
||||
|
||||
$this->assertEquals(
|
||||
0,
|
||||
$store->query_actions(
|
||||
array(
|
||||
'status' => array( ActionScheduler_Store::STATUS_PENDING, ActionScheduler_Store::STATUS_RUNNING ),
|
||||
),
|
||||
'count'
|
||||
)
|
||||
);
|
||||
|
||||
$action_id_1 = $store->save_action( new ActionScheduler_Action( 'my_hook_1', array( 1 ), $schedule ) );
|
||||
$action_id_2 = $store->save_action( new ActionScheduler_Action( 'my_hook_2', array( 1 ), $schedule ) );
|
||||
$action_id_3 = $store->save_action( new ActionScheduler_Action( 'my_hook_3', array( 1 ), $schedule ) );
|
||||
$store->mark_failure( $action_id_3 );
|
||||
|
||||
$this->assertEquals(
|
||||
3,
|
||||
$store->query_actions(
|
||||
array(
|
||||
'status' => array( ActionScheduler_Store::STATUS_PENDING, ActionScheduler_Store::STATUS_FAILED ),
|
||||
),
|
||||
'count'
|
||||
)
|
||||
);
|
||||
$this->assertEquals(
|
||||
2,
|
||||
$store->query_actions(
|
||||
array(
|
||||
'status' => array( ActionScheduler_Store::STATUS_PENDING, ActionScheduler_Store::STATUS_COMPLETE ),
|
||||
),
|
||||
'count'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// phpcs:ignore Squiz.PHP.CommentedOutCode.Found
|
||||
// End tests for \ActionScheduler_Store::query_actions().
|
||||
|
||||
/**
|
||||
* The `has_pending_actions_due` method should return a boolean value depending on whether there are
|
||||
* pending actions.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_has_pending_actions_due() {
|
||||
$store = $this->get_store();
|
||||
$runner = ActionScheduler_Mocker::get_queue_runner( $store );
|
||||
|
||||
for ( $i = - 3; $i <= 3; $i ++ ) {
|
||||
// Some past actions, some future actions.
|
||||
$time = as_get_datetime_object( $i . ' hours' );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$action = new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array( $i ), $schedule, 'my_group' );
|
||||
|
||||
$store->save_action( $action );
|
||||
}
|
||||
$this->assertTrue( $store->has_pending_actions_due() );
|
||||
|
||||
$runner->run();
|
||||
$this->assertFalse( $store->has_pending_actions_due() );
|
||||
}
|
||||
|
||||
/**
|
||||
* The `has_pending_actions_due` method should return false when all pending actions are in the future.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_has_pending_actions_due_only_future_actions() {
|
||||
$store = $this->get_store();
|
||||
|
||||
for ( $i = 1; $i <= 3; $i ++ ) {
|
||||
// Only future actions.
|
||||
$time = as_get_datetime_object( $i . ' hours' );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$action = new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array( $i ), $schedule, 'my_group' );
|
||||
|
||||
$store->save_action( $action );
|
||||
}
|
||||
$this->assertFalse( $store->has_pending_actions_due() );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class ActionScheduler_DBStoreMigrator_Test
|
||||
* @group tables
|
||||
*/
|
||||
class ActionScheduler_DBStoreMigrator_Test extends ActionScheduler_UnitTestCase {
|
||||
|
||||
public function test_create_action_with_last_attempt_date() {
|
||||
$scheduled_date = as_get_datetime_object( strtotime( '-24 hours' ) );
|
||||
$last_attempt_date = as_get_datetime_object( strtotime( '-23 hours' ) );
|
||||
|
||||
$action = new ActionScheduler_FinishedAction( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array(), new ActionScheduler_SimpleSchedule( $scheduled_date ) );
|
||||
$store = new ActionScheduler_DBStoreMigrator();
|
||||
|
||||
$action_id = $store->save_action( $action, null, $last_attempt_date );
|
||||
$action_date = $store->get_date( $action_id );
|
||||
|
||||
$this->assertEquals( $last_attempt_date->format( 'U' ), $action_date->format( 'U' ) );
|
||||
|
||||
$action_id = $store->save_action( $action, $scheduled_date, $last_attempt_date );
|
||||
$action_date = $store->get_date( $action_id );
|
||||
|
||||
$this->assertEquals( $last_attempt_date->format( 'U' ), $action_date->format( 'U' ) );
|
||||
}
|
||||
}
|
||||
814
tests/phpunit/jobstore/ActionScheduler_DBStore_Test.php
Normal file
814
tests/phpunit/jobstore/ActionScheduler_DBStore_Test.php
Normal file
@@ -0,0 +1,814 @@
|
||||
<?php
|
||||
|
||||
use Action_Scheduler\Tests\DataStores\AbstractStoreTest;
|
||||
|
||||
/**
|
||||
* Class ActionScheduler_DBStore_Test
|
||||
* @group tables
|
||||
*/
|
||||
class ActionScheduler_DBStore_Test extends AbstractStoreTest {
|
||||
|
||||
/**
|
||||
* Saved instance of wpdb to restore in cases where we replace the global instance with a mock.
|
||||
*
|
||||
* @see $this->test_db_supports_skip_locked()
|
||||
*
|
||||
* @var \wpdb
|
||||
*/
|
||||
private $original_wpdb;
|
||||
|
||||
public function setUp(): void {
|
||||
global $wpdb;
|
||||
|
||||
// Delete all actions before each test.
|
||||
$wpdb->query( "DELETE FROM {$wpdb->actionscheduler_actions}" );
|
||||
|
||||
parent::setUp();
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the original wpdb global instance for tests that have replaced it.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function tearDown(): void {
|
||||
global $wpdb;
|
||||
if ( null !== $this->original_wpdb ) {
|
||||
$wpdb = $this->original_wpdb;
|
||||
$this->original_wpdb = null;
|
||||
}
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get data store for tests.
|
||||
*
|
||||
* @return ActionScheduler_DBStore
|
||||
*/
|
||||
protected function get_store() {
|
||||
return new ActionScheduler_DBStore();
|
||||
}
|
||||
|
||||
public function test_create_action() {
|
||||
$time = as_get_datetime_object();
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$action = new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array(), $schedule );
|
||||
$store = new ActionScheduler_DBStore();
|
||||
$action_id = $store->save_action( $action );
|
||||
|
||||
$this->assertNotEmpty( $action_id );
|
||||
}
|
||||
|
||||
public function test_create_action_with_scheduled_date() {
|
||||
$time = as_get_datetime_object( strtotime( '-1 week' ) );
|
||||
$action = new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array(), new ActionScheduler_SimpleSchedule( $time ) );
|
||||
$store = new ActionScheduler_DBStore();
|
||||
$action_id = $store->save_action( $action, $time );
|
||||
$action_date = $store->get_date( $action_id );
|
||||
|
||||
$this->assertEquals( $time->format( 'U' ), $action_date->format( 'U' ) );
|
||||
}
|
||||
|
||||
public function test_retrieve_action() {
|
||||
$time = as_get_datetime_object();
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$action = new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array(), $schedule, 'my_group' );
|
||||
$store = new ActionScheduler_DBStore();
|
||||
$action_id = $store->save_action( $action );
|
||||
|
||||
$retrieved = $store->fetch_action( $action_id );
|
||||
$this->assertEquals( $action->get_hook(), $retrieved->get_hook() );
|
||||
$this->assertEqualSets( $action->get_args(), $retrieved->get_args() );
|
||||
$this->assertEquals( $action->get_schedule()->get_date()->format( 'U' ), $retrieved->get_schedule()->get_date()->format( 'U' ) );
|
||||
$this->assertEquals( $action->get_group(), $retrieved->get_group() );
|
||||
}
|
||||
|
||||
public function test_cancel_action() {
|
||||
$time = as_get_datetime_object();
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$action = new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array(), $schedule, 'my_group' );
|
||||
$store = new ActionScheduler_DBStore();
|
||||
$action_id = $store->save_action( $action );
|
||||
$store->cancel_action( $action_id );
|
||||
|
||||
$fetched = $store->fetch_action( $action_id );
|
||||
$this->assertInstanceOf( 'ActionScheduler_CanceledAction', $fetched );
|
||||
}
|
||||
|
||||
public function test_cancel_actions_by_hook() {
|
||||
$store = new ActionScheduler_DBStore();
|
||||
$actions = array();
|
||||
$hook = 'by_hook_test';
|
||||
for ( $day = 1; $day <= 3; $day++ ) {
|
||||
$delta = sprintf( '+%d day', $day );
|
||||
$time = as_get_datetime_object( $delta );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$action = new ActionScheduler_Action( $hook, array(), $schedule, 'my_group' );
|
||||
$actions[] = $store->save_action( $action );
|
||||
}
|
||||
$store->cancel_actions_by_hook( $hook );
|
||||
|
||||
foreach ( $actions as $action_id ) {
|
||||
$fetched = $store->fetch_action( $action_id );
|
||||
$this->assertInstanceOf( 'ActionScheduler_CanceledAction', $fetched );
|
||||
}
|
||||
}
|
||||
|
||||
public function test_cancel_actions_by_group() {
|
||||
$store = new ActionScheduler_DBStore();
|
||||
$actions = array();
|
||||
$group = 'by_group_test';
|
||||
for ( $day = 1; $day <= 3; $day++ ) {
|
||||
$delta = sprintf( '+%d day', $day );
|
||||
$time = as_get_datetime_object( $delta );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$action = new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array(), $schedule, $group );
|
||||
$actions[] = $store->save_action( $action );
|
||||
}
|
||||
$store->cancel_actions_by_group( $group );
|
||||
|
||||
foreach ( $actions as $action_id ) {
|
||||
$fetched = $store->fetch_action( $action_id );
|
||||
$this->assertInstanceOf( 'ActionScheduler_CanceledAction', $fetched );
|
||||
}
|
||||
}
|
||||
|
||||
public function test_claim_actions() {
|
||||
$created_actions = array();
|
||||
$store = new ActionScheduler_DBStore();
|
||||
for ( $i = 3; $i > - 3; $i -- ) {
|
||||
$time = as_get_datetime_object( $i . ' hours' );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$action = new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array( $i ), $schedule, 'my_group' );
|
||||
|
||||
$created_actions[] = $store->save_action( $action );
|
||||
}
|
||||
|
||||
$claim = $store->stake_claim();
|
||||
$this->assertInstanceof( 'ActionScheduler_ActionClaim', $claim );
|
||||
|
||||
$this->assertCount( 3, $claim->get_actions() );
|
||||
$this->assertEqualSets( array_slice( $created_actions, 3, 3 ), $claim->get_actions() );
|
||||
}
|
||||
|
||||
public function test_claim_actions_order() {
|
||||
|
||||
$store = new ActionScheduler_DBStore();
|
||||
$schedule = new ActionScheduler_SimpleSchedule( as_get_datetime_object( '-1 hour' ) );
|
||||
$created_actions = array(
|
||||
$store->save_action( new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array( 1 ), $schedule, 'my_group' ) ),
|
||||
$store->save_action( new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array( 1 ), $schedule, 'my_group' ) ),
|
||||
);
|
||||
|
||||
$claim = $store->stake_claim();
|
||||
$this->assertInstanceof( 'ActionScheduler_ActionClaim', $claim );
|
||||
|
||||
// Verify uniqueness of action IDs.
|
||||
$this->assertEquals( 2, count( array_unique( $created_actions ) ) );
|
||||
|
||||
// Verify the count and order of the actions.
|
||||
$claimed_actions = $claim->get_actions();
|
||||
$this->assertCount( 2, $claimed_actions );
|
||||
$this->assertEquals( $created_actions, $claimed_actions );
|
||||
|
||||
// Verify the reversed order doesn't pass.
|
||||
$reversed_actions = array_reverse( $created_actions );
|
||||
$this->assertNotEquals( $reversed_actions, $claimed_actions );
|
||||
}
|
||||
|
||||
public function test_claim_actions_by_hooks() {
|
||||
$created_actions_by_hook = array();
|
||||
$created_actions = array();
|
||||
|
||||
$store = new ActionScheduler_DBStore();
|
||||
$unique_hook_one = 'my_unique_hook_one';
|
||||
$unique_hook_two = 'my_unique_hook_two';
|
||||
$unique_hooks = array(
|
||||
$unique_hook_one,
|
||||
$unique_hook_two,
|
||||
);
|
||||
|
||||
for ( $i = 3; $i > - 3; $i -- ) {
|
||||
foreach ( $unique_hooks as $unique_hook ) {
|
||||
$time = as_get_datetime_object( $i . ' hours' );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$action = new ActionScheduler_Action( $unique_hook, array( $i ), $schedule, 'my_group' );
|
||||
$action_id = $store->save_action( $action );
|
||||
|
||||
$created_actions[] = $action_id;
|
||||
$created_actions_by_hook[ $unique_hook ][] = $action_id;
|
||||
}
|
||||
}
|
||||
|
||||
$claim = $store->stake_claim( 10, null, $unique_hooks );
|
||||
$this->assertInstanceof( 'ActionScheduler_ActionClaim', $claim );
|
||||
$this->assertCount( 6, $claim->get_actions() );
|
||||
$this->assertEqualSets( array_slice( $created_actions, 6, 6 ), $claim->get_actions() );
|
||||
|
||||
$store->release_claim( $claim );
|
||||
|
||||
$claim = $store->stake_claim( 10, null, array( $unique_hook_one ) );
|
||||
$this->assertInstanceof( 'ActionScheduler_ActionClaim', $claim );
|
||||
$this->assertCount( 3, $claim->get_actions() );
|
||||
$this->assertEqualSets( array_slice( $created_actions_by_hook[ $unique_hook_one ], 3, 3 ), $claim->get_actions() );
|
||||
|
||||
$store->release_claim( $claim );
|
||||
|
||||
$claim = $store->stake_claim( 10, null, array( $unique_hook_two ) );
|
||||
$this->assertInstanceof( 'ActionScheduler_ActionClaim', $claim );
|
||||
$this->assertCount( 3, $claim->get_actions() );
|
||||
$this->assertEqualSets( array_slice( $created_actions_by_hook[ $unique_hook_two ], 3, 3 ), $claim->get_actions() );
|
||||
}
|
||||
|
||||
public function test_claim_actions_by_group() {
|
||||
$created_actions = array();
|
||||
$store = new ActionScheduler_DBStore();
|
||||
$unique_group_one = 'my_unique_group_one';
|
||||
$unique_group_two = 'my_unique_group_two';
|
||||
$unique_groups = array(
|
||||
$unique_group_one,
|
||||
$unique_group_two,
|
||||
);
|
||||
|
||||
for ( $i = 3; $i > - 3; $i -- ) {
|
||||
foreach ( $unique_groups as $unique_group ) {
|
||||
$time = as_get_datetime_object( $i . ' hours' );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$action = new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array( $i ), $schedule, $unique_group );
|
||||
|
||||
$created_actions[ $unique_group ][] = $store->save_action( $action );
|
||||
}
|
||||
}
|
||||
|
||||
$claim = $store->stake_claim( 10, null, array(), $unique_group_one );
|
||||
$this->assertInstanceof( 'ActionScheduler_ActionClaim', $claim );
|
||||
$this->assertCount( 3, $claim->get_actions() );
|
||||
$this->assertEqualSets( array_slice( $created_actions[ $unique_group_one ], 3, 3 ), $claim->get_actions() );
|
||||
|
||||
$store->release_claim( $claim );
|
||||
|
||||
$claim = $store->stake_claim( 10, null, array(), $unique_group_two );
|
||||
$this->assertInstanceof( 'ActionScheduler_ActionClaim', $claim );
|
||||
$this->assertCount( 3, $claim->get_actions() );
|
||||
$this->assertEqualSets( array_slice( $created_actions[ $unique_group_two ], 3, 3 ), $claim->get_actions() );
|
||||
}
|
||||
|
||||
/**
|
||||
* The DBStore allows one or more groups to be excluded from a claim.
|
||||
*/
|
||||
public function test_claim_actions_with_group_exclusions() {
|
||||
$created_actions = array();
|
||||
$store = new ActionScheduler_DBStore();
|
||||
$groups = array( 'foo', 'bar', 'baz' );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( as_get_datetime_object( '-1 hour' ) );
|
||||
|
||||
// Create 6 actions (with 2 in each test group).
|
||||
foreach ( $groups as $group_slug ) {
|
||||
$action = new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array(), $schedule, $group_slug );
|
||||
|
||||
$created_actions[ $group_slug ] = array(
|
||||
$store->save_action( $action ),
|
||||
$store->save_action( $action ),
|
||||
);
|
||||
}
|
||||
|
||||
// If we exclude group 'foo' (representing 2 actions) the remaining 4 actions from groups 'bar' and 'baz' should still be claimed.
|
||||
$store->set_claim_filter( 'exclude-groups', 'foo' );
|
||||
$claim = $store->stake_claim();
|
||||
$this->assertEquals(
|
||||
array_merge( $created_actions['bar'], $created_actions['baz'] ),
|
||||
$claim->get_actions(),
|
||||
'A single group can successfully be excluded from claims.'
|
||||
);
|
||||
$store->release_claim( $claim );
|
||||
|
||||
// If we exclude groups 'bar' and 'baz' (representing 4 actions) the remaining 2 actions from group 'foo' should still be claimed.
|
||||
$store->set_claim_filter( 'exclude-groups', array( 'bar', 'baz' ) );
|
||||
$claim = $store->stake_claim();
|
||||
$this->assertEquals(
|
||||
$created_actions['foo'],
|
||||
$claim->get_actions(),
|
||||
'Multiple groups can successfully be excluded from claims.'
|
||||
);
|
||||
$store->release_claim( $claim );
|
||||
|
||||
// If we include group 'foo' (representing 2 actions) after excluding all groups, the inclusion should 'win'.
|
||||
$store->set_claim_filter( 'exclude-groups', array( 'foo', 'bar', 'baz' ) );
|
||||
$claim = $store->stake_claim( 10, null, array(), 'foo' );
|
||||
$this->assertEquals(
|
||||
$created_actions['foo'],
|
||||
$claim->get_actions(),
|
||||
'Including a specific group takes precedence over group exclusions.'
|
||||
);
|
||||
$store->release_claim( $claim );
|
||||
}
|
||||
|
||||
public function test_claim_actions_by_hook_and_group() {
|
||||
$created_actions_by_hook = array();
|
||||
$created_actions = array();
|
||||
$store = new ActionScheduler_DBStore();
|
||||
|
||||
$unique_hook_one = 'my_other_unique_hook_one';
|
||||
$unique_hook_two = 'my_other_unique_hook_two';
|
||||
$unique_hooks = array(
|
||||
$unique_hook_one,
|
||||
$unique_hook_two,
|
||||
);
|
||||
|
||||
$unique_group_one = 'my_other_other_unique_group_one';
|
||||
$unique_group_two = 'my_other_unique_group_two';
|
||||
$unique_groups = array(
|
||||
$unique_group_one,
|
||||
$unique_group_two,
|
||||
);
|
||||
|
||||
for ( $i = 3; $i > - 3; $i -- ) {
|
||||
foreach ( $unique_hooks as $unique_hook ) {
|
||||
foreach ( $unique_groups as $unique_group ) {
|
||||
$time = as_get_datetime_object( $i . ' hours' );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$action = new ActionScheduler_Action( $unique_hook, array( $i ), $schedule, $unique_group );
|
||||
$action_id = $store->save_action( $action );
|
||||
|
||||
$created_actions[ $unique_group ][] = $action_id;
|
||||
$created_actions_by_hook[ $unique_hook ][ $unique_group ][] = $action_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Test Both Hooks with Each Group */
|
||||
|
||||
$claim = $store->stake_claim( 10, null, $unique_hooks, $unique_group_one );
|
||||
$this->assertInstanceof( 'ActionScheduler_ActionClaim', $claim );
|
||||
$this->assertCount( 6, $claim->get_actions() );
|
||||
$this->assertEqualSets( array_slice( $created_actions[ $unique_group_one ], 6, 6 ), $claim->get_actions() );
|
||||
|
||||
$store->release_claim( $claim );
|
||||
|
||||
$claim = $store->stake_claim( 10, null, $unique_hooks, $unique_group_two );
|
||||
$this->assertInstanceof( 'ActionScheduler_ActionClaim', $claim );
|
||||
$this->assertCount( 6, $claim->get_actions() );
|
||||
$this->assertEqualSets( array_slice( $created_actions[ $unique_group_two ], 6, 6 ), $claim->get_actions() );
|
||||
|
||||
$store->release_claim( $claim );
|
||||
|
||||
/** Test Just One Hook with Group One */
|
||||
|
||||
$claim = $store->stake_claim( 10, null, array( $unique_hook_one ), $unique_group_one );
|
||||
$this->assertInstanceof( 'ActionScheduler_ActionClaim', $claim );
|
||||
$this->assertCount( 3, $claim->get_actions() );
|
||||
$this->assertEqualSets( array_slice( $created_actions_by_hook[ $unique_hook_one ][ $unique_group_one ], 3, 3 ), $claim->get_actions() );
|
||||
|
||||
$store->release_claim( $claim );
|
||||
|
||||
$claim = $store->stake_claim( 24, null, array( $unique_hook_two ), $unique_group_one );
|
||||
$this->assertInstanceof( 'ActionScheduler_ActionClaim', $claim );
|
||||
$this->assertCount( 3, $claim->get_actions() );
|
||||
$this->assertEqualSets( array_slice( $created_actions_by_hook[ $unique_hook_two ][ $unique_group_one ], 3, 3 ), $claim->get_actions() );
|
||||
|
||||
$store->release_claim( $claim );
|
||||
|
||||
/** Test Just One Hook with Group Two */
|
||||
|
||||
$claim = $store->stake_claim( 10, null, array( $unique_hook_one ), $unique_group_two );
|
||||
$this->assertInstanceof( 'ActionScheduler_ActionClaim', $claim );
|
||||
$this->assertCount( 3, $claim->get_actions() );
|
||||
$this->assertEqualSets( array_slice( $created_actions_by_hook[ $unique_hook_one ][ $unique_group_two ], 3, 3 ), $claim->get_actions() );
|
||||
|
||||
$store->release_claim( $claim );
|
||||
|
||||
$claim = $store->stake_claim( 24, null, array( $unique_hook_two ), $unique_group_two );
|
||||
$this->assertInstanceof( 'ActionScheduler_ActionClaim', $claim );
|
||||
$this->assertCount( 3, $claim->get_actions() );
|
||||
$this->assertEqualSets( array_slice( $created_actions_by_hook[ $unique_hook_two ][ $unique_group_two ], 3, 3 ), $claim->get_actions() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm that priorities are respected when claiming actions.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_claim_actions_respecting_priority() {
|
||||
$store = new ActionScheduler_DBStore();
|
||||
|
||||
$schedule = new ActionScheduler_SimpleSchedule( as_get_datetime_object( '-2 hours' ) );
|
||||
$routine_action_1 = $store->save_action( new ActionScheduler_Action( 'routine_past_due', array(), $schedule, '' ) );
|
||||
|
||||
$schedule = new ActionScheduler_SimpleSchedule( as_get_datetime_object( '-1 hour' ) );
|
||||
$action = new ActionScheduler_Action( 'high_priority_past_due', array(), $schedule, '' );
|
||||
$action->set_priority( 5 );
|
||||
$priority_action = $store->save_action( $action );
|
||||
|
||||
$schedule = new ActionScheduler_SimpleSchedule( as_get_datetime_object( '-4 hours' ) );
|
||||
$routine_action_2 = $store->save_action( new ActionScheduler_Action( 'routine_past_due', array(), $schedule, '' ) );
|
||||
|
||||
$schedule = new ActionScheduler_SimpleSchedule( as_get_datetime_object( '+1 hour' ) );
|
||||
$action = new ActionScheduler_Action( 'high_priority_future', array(), $schedule, '' );
|
||||
$action->set_priority( 2 );
|
||||
$priority_future_action = $store->save_action( $action );
|
||||
|
||||
$claim = $store->stake_claim();
|
||||
$this->assertEquals(
|
||||
array( $priority_action, $routine_action_2, $routine_action_1 ),
|
||||
$claim->get_actions(),
|
||||
'High priority actions take precedence over older but lower priority actions.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The query used to claim actions explicitly ignores future pending actions, but it
|
||||
* is still possible under unusual conditions (such as if MySQL runs out of temporary
|
||||
* storage space) for such actions to be returned.
|
||||
*
|
||||
* When this happens, we still expect the store to filter them out, otherwise there is
|
||||
* a risk that actions will be unexpectedly processed ahead of time.
|
||||
*
|
||||
* @see https://github.com/woocommerce/action-scheduler/issues/634
|
||||
*/
|
||||
public function test_claim_filters_out_unexpected_future_actions() {
|
||||
$group = __METHOD__;
|
||||
$store = new ActionScheduler_DBStore();
|
||||
|
||||
// Create 4 actions: 2 that are already due (-3hrs and -1hrs) and 2 that are not yet due (+1hr and +3hrs).
|
||||
for ( $i = -3; $i <= 3; $i += 2 ) {
|
||||
$schedule = new ActionScheduler_SimpleSchedule( as_get_datetime_object( $i . ' hours' ) );
|
||||
$action_ids[] = $store->save_action( new ActionScheduler_Action( 'test_' . $i, array(), $schedule, $group ) );
|
||||
}
|
||||
|
||||
// This callback is used to simulate the unusual conditions whereby MySQL might unexpectedly return future
|
||||
// actions, contrary to the conditions used by the store object when staking its claim.
|
||||
$simulate_unexpected_db_behavior = function( $sql ) use ( $action_ids ) {
|
||||
global $wpdb;
|
||||
|
||||
// Look out for the claim update query, ignore all others.
|
||||
if (
|
||||
0 !== strpos( $sql, "UPDATE $wpdb->actionscheduler_actions" )
|
||||
|| ! preg_match( "/claim_id = 0 AND scheduled_date_gmt <= '([0-9:\-\s]{19})'/", $sql, $matches )
|
||||
|| count( $matches ) !== 2
|
||||
) {
|
||||
return $sql;
|
||||
}
|
||||
|
||||
// Now modify the query, forcing it to also return the future actions we created.
|
||||
return str_replace( $matches[1], as_get_datetime_object( '+4 hours' )->format( 'Y-m-d H:i:s' ), $sql );
|
||||
};
|
||||
|
||||
add_filter( 'query', $simulate_unexpected_db_behavior );
|
||||
$claim = $store->stake_claim( 10, null, array(), $group );
|
||||
$claimed_actions = $claim->get_actions();
|
||||
$this->assertCount( 2, $claimed_actions );
|
||||
|
||||
// Cleanup.
|
||||
remove_filter( 'query', $simulate_unexpected_db_behavior );
|
||||
$store->release_claim( $claim );
|
||||
}
|
||||
|
||||
public function test_duplicate_claim() {
|
||||
$created_actions = array();
|
||||
$store = new ActionScheduler_DBStore();
|
||||
for ( $i = 0; $i > - 3; $i -- ) {
|
||||
$time = as_get_datetime_object( $i . ' hours' );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$action = new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array( $i ), $schedule, 'my_group' );
|
||||
|
||||
$created_actions[] = $store->save_action( $action );
|
||||
}
|
||||
|
||||
$claim1 = $store->stake_claim();
|
||||
$claim2 = $store->stake_claim();
|
||||
$this->assertCount( 3, $claim1->get_actions() );
|
||||
$this->assertCount( 0, $claim2->get_actions() );
|
||||
}
|
||||
|
||||
public function test_release_claim() {
|
||||
$created_actions = array();
|
||||
$store = new ActionScheduler_DBStore();
|
||||
for ( $i = 0; $i > - 3; $i -- ) {
|
||||
$time = as_get_datetime_object( $i . ' hours' );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$action = new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array( $i ), $schedule, 'my_group' );
|
||||
|
||||
$created_actions[] = $store->save_action( $action );
|
||||
}
|
||||
|
||||
$claim1 = $store->stake_claim();
|
||||
|
||||
$store->release_claim( $claim1 );
|
||||
$this->assertCount( 0, $store->find_actions_by_claim_id( $claim1->get_id() ) );
|
||||
|
||||
$claim2 = $store->stake_claim();
|
||||
$this->assertCount( 3, $claim2->get_actions() );
|
||||
$store->release_claim( $claim2 );
|
||||
$this->assertCount( 0, $store->find_actions_by_claim_id( $claim1->get_id() ) );
|
||||
|
||||
}
|
||||
|
||||
public function test_search() {
|
||||
$created_actions = array();
|
||||
$store = new ActionScheduler_DBStore();
|
||||
for ( $i = - 3; $i <= 3; $i ++ ) {
|
||||
$time = as_get_datetime_object( $i . ' hours' );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$action = new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array( $i ), $schedule, 'my_group' );
|
||||
|
||||
$created_actions[] = $store->save_action( $action );
|
||||
}
|
||||
|
||||
$next_no_args = $store->find_action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK );
|
||||
$this->assertEquals( $created_actions[0], $next_no_args );
|
||||
|
||||
$next_with_args = $store->find_action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array( 'args' => array( 1 ) ) );
|
||||
$this->assertEquals( $created_actions[4], $next_with_args );
|
||||
|
||||
$non_existent = $store->find_action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array( 'args' => array( 17 ) ) );
|
||||
$this->assertNull( $non_existent );
|
||||
}
|
||||
|
||||
public function test_search_by_group() {
|
||||
$store = new ActionScheduler_DBStore();
|
||||
$schedule = new ActionScheduler_SimpleSchedule( as_get_datetime_object( 'tomorrow' ) );
|
||||
|
||||
$abc = $store->save_action( new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array( 1 ), $schedule, 'abc' ) );
|
||||
$def = $store->save_action( new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array( 1 ), $schedule, 'def' ) );
|
||||
$ghi = $store->save_action( new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array( 1 ), $schedule, 'ghi' ) );
|
||||
|
||||
$this->assertEquals( $abc, $store->find_action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array( 'group' => 'abc' ) ) );
|
||||
$this->assertEquals( $def, $store->find_action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array( 'group' => 'def' ) ) );
|
||||
$this->assertEquals( $ghi, $store->find_action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array( 'group' => 'ghi' ) ) );
|
||||
}
|
||||
|
||||
public function test_get_run_date() {
|
||||
$time = as_get_datetime_object( '-10 minutes' );
|
||||
$schedule = new ActionScheduler_IntervalSchedule( $time, HOUR_IN_SECONDS );
|
||||
$action = new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array(), $schedule );
|
||||
$store = new ActionScheduler_DBStore();
|
||||
$action_id = $store->save_action( $action );
|
||||
|
||||
$this->assertEquals( $time->format( 'U' ), $store->get_date( $action_id )->format( 'U' ) );
|
||||
|
||||
$action = $store->fetch_action( $action_id );
|
||||
$action->execute();
|
||||
$now = as_get_datetime_object();
|
||||
$store->mark_complete( $action_id );
|
||||
|
||||
$this->assertEquals( $now->format( 'U' ), $store->get_date( $action_id )->format( 'U' ) );
|
||||
|
||||
$next = $action->get_schedule()->get_next( $now );
|
||||
$new_action_id = $store->save_action( $action, $next );
|
||||
|
||||
$this->assertEquals( (int) ( $now->format( 'U' ) ) + HOUR_IN_SECONDS, $store->get_date( $new_action_id )->format( 'U' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test creating a unique action.
|
||||
*/
|
||||
public function test_create_action_unique() {
|
||||
$time = as_get_datetime_object();
|
||||
$hook = md5( wp_rand() );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$store = new ActionScheduler_DBStore();
|
||||
$action = new ActionScheduler_Action( $hook, array(), $schedule );
|
||||
|
||||
$action_id = $store->save_action( $action );
|
||||
$this->assertNotEquals( 0, $action_id );
|
||||
$action_from_db = $store->fetch_action( $action_id );
|
||||
$this->assertTrue( is_a( $action_from_db, ActionScheduler_Action::class ) );
|
||||
|
||||
$action = new ActionScheduler_Action( $hook, array(), $schedule );
|
||||
$action_id_duplicate = $store->save_unique_action( $action );
|
||||
$this->assertEquals( 0, $action_id_duplicate );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test saving unique actions across different groups. Different groups should be saved, same groups shouldn't.
|
||||
*/
|
||||
public function test_create_action_unique_with_different_groups() {
|
||||
$time = as_get_datetime_object();
|
||||
$hook = md5( wp_rand() );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$store = new ActionScheduler_DBStore();
|
||||
$action = new ActionScheduler_Action( $hook, array(), $schedule, 'group1' );
|
||||
|
||||
$action_id = $store->save_action( $action );
|
||||
$action_from_db = $store->fetch_action( $action_id );
|
||||
$this->assertNotEquals( 0, $action_id );
|
||||
$this->assertTrue( is_a( $action_from_db, ActionScheduler_Action::class ) );
|
||||
|
||||
$action2 = new ActionScheduler_Action( $hook, array(), $schedule, 'group2' );
|
||||
$action_id_group2 = $store->save_unique_action( $action2 );
|
||||
$this->assertNotEquals( 0, $action_id_group2 );
|
||||
$action_2_from_db = $store->fetch_action( $action_id_group2 );
|
||||
$this->assertTrue( is_a( $action_2_from_db, ActionScheduler_Action::class ) );
|
||||
|
||||
$action3 = new ActionScheduler_Action( $hook, array(), $schedule, 'group2' );
|
||||
$action_id_group2_double = $store->save_unique_action( $action3 );
|
||||
$this->assertEquals( 0, $action_id_group2_double );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test saving a unique action first, and then successfully scheduling a non-unique action.
|
||||
*/
|
||||
public function test_create_action_unique_and_then_non_unique() {
|
||||
$time = as_get_datetime_object();
|
||||
$hook = md5( wp_rand() );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$store = new ActionScheduler_DBStore();
|
||||
$action = new ActionScheduler_Action( $hook, array(), $schedule );
|
||||
|
||||
$action_id = $store->save_unique_action( $action );
|
||||
$this->assertNotEquals( 0, $action_id );
|
||||
$action_from_db = $store->fetch_action( $action_id );
|
||||
$this->assertTrue( is_a( $action_from_db, ActionScheduler_Action::class ) );
|
||||
|
||||
// Non unique action is scheduled even if the previous one was unique.
|
||||
$action = new ActionScheduler_Action( $hook, array(), $schedule );
|
||||
$action_id_duplicate = $store->save_action( $action );
|
||||
$this->assertNotEquals( 0, $action_id_duplicate );
|
||||
$action_from_db_duplicate = $store->fetch_action( $action_id_duplicate );
|
||||
$this->assertTrue( is_a( $action_from_db_duplicate, ActionScheduler_Action::class ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test asserting that action when an action is created with empty args, it matches with actions created with args for uniqueness.
|
||||
*/
|
||||
public function test_create_action_unique_with_empty_array() {
|
||||
$time = as_get_datetime_object();
|
||||
$hook = md5( wp_rand() );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$store = new ActionScheduler_DBStore();
|
||||
$action = new ActionScheduler_Action( $hook, array( 'foo' => 'bar' ), $schedule );
|
||||
|
||||
$action_id = $store->save_unique_action( $action );
|
||||
$this->assertNotEquals( 0, $action_id );
|
||||
$action_from_db = $store->fetch_action( $action_id );
|
||||
$this->assertTrue( is_a( $action_from_db, ActionScheduler_Action::class ) );
|
||||
|
||||
$action_with_empty_args = new ActionScheduler_Action( $hook, array(), $schedule );
|
||||
$action_id_duplicate = $store->save_unique_action( $action_with_empty_args );
|
||||
$this->assertEquals( 0, $action_id_duplicate );
|
||||
}
|
||||
|
||||
/**
|
||||
* Uniqueness does not check for args, so actions with different args can't be scheduled when unique is true.
|
||||
*/
|
||||
public function test_create_action_unique_with_different_args_still_fail() {
|
||||
$time = as_get_datetime_object();
|
||||
$hook = md5( wp_rand() );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$store = new ActionScheduler_DBStore();
|
||||
$action = new ActionScheduler_Action( $hook, array( 'foo' => 'bar' ), $schedule );
|
||||
|
||||
$action_id = $store->save_unique_action( $action );
|
||||
$this->assertNotEquals( 0, $action_id );
|
||||
$action_from_db = $store->fetch_action( $action_id );
|
||||
$this->assertTrue( is_a( $action_from_db, ActionScheduler_Action::class ) );
|
||||
|
||||
$action_with_diff_args = new ActionScheduler_Action( $hook, array( 'foo' => 'bazz' ), $schedule );
|
||||
$action_id_duplicate = $store->save_unique_action( $action_with_diff_args );
|
||||
$this->assertEquals( 0, $action_id_duplicate );
|
||||
}
|
||||
|
||||
/**
|
||||
* When a set of claimed actions are processed, they should be executed in the expected order (by priority,
|
||||
* then by least number of attempts, then by scheduled date, then finally by action ID).
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_actions_are_processed_in_correct_order() {
|
||||
global $wpdb;
|
||||
|
||||
$now = time();
|
||||
$actual_order = array();
|
||||
|
||||
// When `foo` actions are processed, record the sequence number they supply.
|
||||
$watcher = function ( $number ) use ( &$actual_order ) {
|
||||
$actual_order[] = $number;
|
||||
};
|
||||
|
||||
as_schedule_single_action( $now - 10, 'foo', array( 4 ), '', false, 10 );
|
||||
as_schedule_single_action( $now - 20, 'foo', array( 3 ), '', false, 10 );
|
||||
as_schedule_single_action( $now - 5, 'foo', array( 2 ), '', false, 5 );
|
||||
as_schedule_single_action( $now - 20, 'foo', array( 1 ), '', false, 5 );
|
||||
$reattempted = as_schedule_single_action( $now - 40, 'foo', array( 7 ), '', false, 20 );
|
||||
as_schedule_single_action( $now - 40, 'foo', array( 5 ), '', false, 20 );
|
||||
as_schedule_single_action( $now - 40, 'foo', array( 6 ), '', false, 20 );
|
||||
|
||||
// Modify the `attempt` count on one of our test actions, to change expectations about its execution order.
|
||||
$wpdb->update(
|
||||
$wpdb->actionscheduler_actions,
|
||||
array( 'attempts' => 5 ),
|
||||
array( 'action_id' => $reattempted )
|
||||
);
|
||||
|
||||
add_action( 'foo', $watcher );
|
||||
ActionScheduler_Mocker::get_queue_runner( ActionScheduler::store() )->run();
|
||||
remove_action( 'foo', $watcher );
|
||||
|
||||
$this->assertEquals( range( 1, 7 ), $actual_order, 'When a claim is processed, individual actions execute in the expected order.' );
|
||||
}
|
||||
|
||||
/**
|
||||
* When a set of claimed actions are processed, they should be executed in the expected order (by priority,
|
||||
* then by least number of attempts, then by scheduled date, then finally by action ID). This should be true
|
||||
* even if actions are scheduled from within other scheduled actions.
|
||||
*
|
||||
* This test is a variation of `test_actions_are_processed_in_correct_order`, see discussion in
|
||||
* https://github.com/woocommerce/action-scheduler/issues/951 to see why this specific nuance is tested.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_child_actions_are_processed_in_correct_order() {
|
||||
$time = time() - 10;
|
||||
$actual_order = array();
|
||||
$watcher = function ( $number ) use ( &$actual_order ) {
|
||||
$actual_order[] = $number;
|
||||
};
|
||||
$parent_action = function () use ( $time ) {
|
||||
// We generate 20 test actions because this is optimal for reproducing the conditions in the
|
||||
// linked bug report. With fewer actions, the error condition is less likely to surface.
|
||||
for ( $i = 1; $i <= 20; $i++ ) {
|
||||
as_schedule_single_action( $time, 'foo', array( $i ) );
|
||||
}
|
||||
};
|
||||
|
||||
add_action( 'foo', $watcher );
|
||||
add_action( 'parent', $parent_action );
|
||||
|
||||
as_schedule_single_action( $time, 'parent' );
|
||||
ActionScheduler_Mocker::get_queue_runner( ActionScheduler::store() )->run();
|
||||
|
||||
remove_action( 'foo', $watcher );
|
||||
add_action( 'parent', $parent_action );
|
||||
|
||||
$this->assertEquals( range( 1, 20 ), $actual_order, 'Once claimed, scheduled actions are executed in the expected order, including if "child actions" are scheduled from within another action.' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $expected_result
|
||||
* @param string $db_server_info
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @dataProvider db_supports_skip_locked_provider
|
||||
*/
|
||||
public function test_db_supports_skip_locked( bool $expected_result, string $db_server_info ) {
|
||||
global $wpdb;
|
||||
|
||||
// Stash the original since we're overwriting it with a partial mock. Self::tear_down() will restore this.
|
||||
$this->original_wpdb = $wpdb;
|
||||
|
||||
$wpdb = $this->getMockBuilder( get_class( $wpdb ) )
|
||||
->setMethods( [ 'db_server_info' ] )
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$wpdb->method( 'db_server_info' )->willReturn( $db_server_info );
|
||||
|
||||
$reflection = new \ReflectionClass( ActionScheduler_DBStore::class );
|
||||
$method = $reflection->getMethod( 'db_supports_skip_locked' );
|
||||
$method->setAccessible( true );
|
||||
$db_store = new ActionScheduler_DBStore();
|
||||
$this->assertSame( $expected_result, $method->invoke( $db_store ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Data Provider for ::test_db_supports_skip_locked().
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
public static function db_supports_skip_locked_provider(): array {
|
||||
// PHP <= 8.0.15 didn't strip the 5.5.5- prefix for MariaDB.
|
||||
$maria_db_prefix = PHP_VERSION_ID < 80016 ? '5.5.5-' : '';
|
||||
|
||||
return array(
|
||||
'MySQL 5.6.1 does not support skip locked' => array(
|
||||
false,
|
||||
'5.6.1'
|
||||
),
|
||||
'MySQL 8.0.0 does not support skip locked' => array(
|
||||
false,
|
||||
'8.0.0'
|
||||
),
|
||||
'MySQL 8.0.1 does support skip locked' => array(
|
||||
true,
|
||||
'8.0.1'
|
||||
),
|
||||
'MySQL 8.4.4 does support skip locked' => array(
|
||||
true,
|
||||
'8.4.4'
|
||||
),
|
||||
'MariaDB 10.5.0 does not support skip locked' => array(
|
||||
false,
|
||||
$maria_db_prefix . '10.5.0-MariaDB'
|
||||
),
|
||||
'MariaDB 10.6.0 does support skip locked' => array(
|
||||
true,
|
||||
$maria_db_prefix . '10.6.0-MariaDB'
|
||||
),
|
||||
'MariaDB 11.5.0 does support skip locked' => array(
|
||||
true,
|
||||
$maria_db_prefix . '11.5.0-MariaDB'
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
277
tests/phpunit/jobstore/ActionScheduler_HybridStore_Test.php
Normal file
277
tests/phpunit/jobstore/ActionScheduler_HybridStore_Test.php
Normal file
@@ -0,0 +1,277 @@
|
||||
<?php
|
||||
|
||||
|
||||
use Action_Scheduler\Migration\Config;
|
||||
use ActionScheduler_NullAction as NullAction;
|
||||
use ActionScheduler_wpCommentLogger as CommentLogger;
|
||||
use ActionScheduler_wpPostStore as PostStore;
|
||||
|
||||
/**
|
||||
* Class ActionScheduler_HybridStore_Test
|
||||
* @group tables
|
||||
*/
|
||||
class ActionScheduler_HybridStore_Test extends ActionScheduler_UnitTestCase {
|
||||
/** @var int */
|
||||
private $demarkation_id = 1000;
|
||||
|
||||
public function setUp(): void {
|
||||
parent::setUp();
|
||||
if ( ! taxonomy_exists( PostStore::GROUP_TAXONOMY ) ) {
|
||||
// register the post type and taxonomy necessary for the store to work.
|
||||
$store = new PostStore();
|
||||
$store->init();
|
||||
}
|
||||
update_option( ActionScheduler_HybridStore::DEMARKATION_OPTION, $this->demarkation_id );
|
||||
$hybrid = new ActionScheduler_HybridStore();
|
||||
$hybrid->set_autoincrement( '', ActionScheduler_StoreSchema::ACTIONS_TABLE );
|
||||
}
|
||||
|
||||
public function tearDown(): void {
|
||||
parent::tearDown();
|
||||
|
||||
// reset the autoincrement index.
|
||||
/** @var \wpdb $wpdb */
|
||||
global $wpdb;
|
||||
$wpdb->query( "TRUNCATE TABLE {$wpdb->actionscheduler_actions}" );
|
||||
$wpdb->query( "TRUNCATE TABLE {$wpdb->actionscheduler_logs}" );
|
||||
delete_option( ActionScheduler_HybridStore::DEMARKATION_OPTION );
|
||||
}
|
||||
|
||||
public function test_actions_are_migrated_on_find() {
|
||||
$source_store = new PostStore();
|
||||
$destination_store = new ActionScheduler_DBStore();
|
||||
$source_logger = new CommentLogger();
|
||||
$destination_logger = new ActionScheduler_DBLogger();
|
||||
|
||||
$config = new Config();
|
||||
$config->set_source_store( $source_store );
|
||||
$config->set_source_logger( $source_logger );
|
||||
$config->set_destination_store( $destination_store );
|
||||
$config->set_destination_logger( $destination_logger );
|
||||
|
||||
$hybrid_store = new ActionScheduler_HybridStore( $config );
|
||||
|
||||
$time = as_get_datetime_object( '10 minutes ago' );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$action = new ActionScheduler_Action( __FUNCTION__, array(), $schedule );
|
||||
$source_id = $source_store->save_action( $action );
|
||||
|
||||
$found = $hybrid_store->find_action( __FUNCTION__, array() );
|
||||
|
||||
$this->assertNotEquals( $source_id, $found );
|
||||
$this->assertGreaterThanOrEqual( $this->demarkation_id, $found );
|
||||
|
||||
$found_in_source = $source_store->fetch_action( $source_id );
|
||||
$this->assertInstanceOf( NullAction::class, $found_in_source );
|
||||
}
|
||||
|
||||
public function test_actions_are_migrated_on_query() {
|
||||
$source_store = new PostStore();
|
||||
$destination_store = new ActionScheduler_DBStore();
|
||||
$source_logger = new CommentLogger();
|
||||
$destination_logger = new ActionScheduler_DBLogger();
|
||||
|
||||
$config = new Config();
|
||||
$config->set_source_store( $source_store );
|
||||
$config->set_source_logger( $source_logger );
|
||||
$config->set_destination_store( $destination_store );
|
||||
$config->set_destination_logger( $destination_logger );
|
||||
|
||||
$hybrid_store = new ActionScheduler_HybridStore( $config );
|
||||
|
||||
$source_actions = array();
|
||||
$destination_actions = array();
|
||||
|
||||
for ( $i = 0; $i < 10; $i++ ) {
|
||||
// create in instance in the source store.
|
||||
$time = as_get_datetime_object( ( $i * 10 + 1 ) . ' minutes' );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$action = new ActionScheduler_Action( __FUNCTION__, array(), $schedule );
|
||||
|
||||
$source_actions[] = $source_store->save_action( $action );
|
||||
|
||||
// create an instance in the destination store.
|
||||
$time = as_get_datetime_object( ( $i * 10 + 5 ) . ' minutes' );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$action = new ActionScheduler_Action( __FUNCTION__, array(), $schedule );
|
||||
|
||||
$destination_actions[] = $destination_store->save_action( $action );
|
||||
}
|
||||
|
||||
$found = $hybrid_store->query_actions(
|
||||
array(
|
||||
'hook' => __FUNCTION__,
|
||||
'per_page' => 6,
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertCount( 6, $found );
|
||||
foreach ( $found as $key => $action_id ) {
|
||||
$this->assertNotContains( $action_id, $source_actions );
|
||||
$this->assertGreaterThanOrEqual( $this->demarkation_id, $action_id );
|
||||
if ( 0 === $key % 2 ) { // it should have been in the source store.
|
||||
$this->assertNotContains( $action_id, $destination_actions );
|
||||
} else { // it should have already been in the destination store.
|
||||
$this->assertContains( $action_id, $destination_actions );
|
||||
}
|
||||
}
|
||||
|
||||
// six of the original 10 should have migrated to the new store,
|
||||
// even though only three were retrieve in the final query.
|
||||
$found_in_source = $source_store->query_actions(
|
||||
array(
|
||||
'hook' => __FUNCTION__,
|
||||
'per_page' => 10,
|
||||
)
|
||||
);
|
||||
$this->assertCount( 4, $found_in_source );
|
||||
}
|
||||
|
||||
|
||||
public function test_actions_are_migrated_on_claim() {
|
||||
$source_store = new PostStore();
|
||||
$destination_store = new ActionScheduler_DBStore();
|
||||
$source_logger = new CommentLogger();
|
||||
$destination_logger = new ActionScheduler_DBLogger();
|
||||
|
||||
$config = new Config();
|
||||
$config->set_source_store( $source_store );
|
||||
$config->set_source_logger( $source_logger );
|
||||
$config->set_destination_store( $destination_store );
|
||||
$config->set_destination_logger( $destination_logger );
|
||||
|
||||
$hybrid_store = new ActionScheduler_HybridStore( $config );
|
||||
|
||||
$source_actions = array();
|
||||
$destination_actions = array();
|
||||
|
||||
for ( $i = 0; $i < 10; $i++ ) {
|
||||
// create in instance in the source store.
|
||||
$time = as_get_datetime_object( ( $i * 10 + 1 ) . ' minutes ago' );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$action = new ActionScheduler_Action( __FUNCTION__, array(), $schedule );
|
||||
|
||||
$source_actions[] = $source_store->save_action( $action );
|
||||
|
||||
// create an instance in the destination store.
|
||||
$time = as_get_datetime_object( ( $i * 10 + 5 ) . ' minutes ago' );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$action = new ActionScheduler_Action( __FUNCTION__, array(), $schedule );
|
||||
|
||||
$destination_actions[] = $destination_store->save_action( $action );
|
||||
}
|
||||
|
||||
$claim = $hybrid_store->stake_claim( 6 );
|
||||
|
||||
$claimed_actions = $claim->get_actions();
|
||||
$this->assertCount( 6, $claimed_actions );
|
||||
$this->assertCount( 3, array_intersect( $destination_actions, $claimed_actions ) );
|
||||
|
||||
// six of the original 10 should have migrated to the new store,
|
||||
// even though only three were retrieve in the final claim.
|
||||
$found_in_source = $source_store->query_actions(
|
||||
array(
|
||||
'hook' => __FUNCTION__,
|
||||
'per_page' => 10,
|
||||
)
|
||||
);
|
||||
$this->assertCount( 4, $found_in_source );
|
||||
|
||||
$this->assertEquals( 0, $source_store->get_claim_count() );
|
||||
$this->assertEquals( 1, $destination_store->get_claim_count() );
|
||||
$this->assertEquals( 1, $hybrid_store->get_claim_count() );
|
||||
|
||||
}
|
||||
|
||||
public function test_fetch_respects_demarkation() {
|
||||
$source_store = new PostStore();
|
||||
$destination_store = new ActionScheduler_DBStore();
|
||||
$source_logger = new CommentLogger();
|
||||
$destination_logger = new ActionScheduler_DBLogger();
|
||||
|
||||
$config = new Config();
|
||||
$config->set_source_store( $source_store );
|
||||
$config->set_source_logger( $source_logger );
|
||||
$config->set_destination_store( $destination_store );
|
||||
$config->set_destination_logger( $destination_logger );
|
||||
|
||||
$hybrid_store = new ActionScheduler_HybridStore( $config );
|
||||
|
||||
$source_actions = array();
|
||||
$destination_actions = array();
|
||||
|
||||
for ( $i = 0; $i < 2; $i++ ) {
|
||||
// create in instance in the source store.
|
||||
$time = as_get_datetime_object( ( $i * 10 + 1 ) . ' minutes ago' );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$action = new ActionScheduler_Action( __FUNCTION__, array(), $schedule );
|
||||
|
||||
$source_actions[] = $source_store->save_action( $action );
|
||||
|
||||
// create an instance in the destination store.
|
||||
$time = as_get_datetime_object( ( $i * 10 + 5 ) . ' minutes ago' );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$action = new ActionScheduler_Action( __FUNCTION__, array(), $schedule );
|
||||
|
||||
$destination_actions[] = $destination_store->save_action( $action );
|
||||
}
|
||||
|
||||
foreach ( $source_actions as $action_id ) {
|
||||
$action = $hybrid_store->fetch_action( $action_id );
|
||||
$this->assertInstanceOf( ActionScheduler_Action::class, $action );
|
||||
$this->assertNotInstanceOf( NullAction::class, $action );
|
||||
}
|
||||
|
||||
foreach ( $destination_actions as $action_id ) {
|
||||
$action = $hybrid_store->fetch_action( $action_id );
|
||||
$this->assertInstanceOf( ActionScheduler_Action::class, $action );
|
||||
$this->assertNotInstanceOf( NullAction::class, $action );
|
||||
}
|
||||
}
|
||||
|
||||
public function test_mark_complete_respects_demarkation() {
|
||||
$source_store = new PostStore();
|
||||
$destination_store = new ActionScheduler_DBStore();
|
||||
$source_logger = new CommentLogger();
|
||||
$destination_logger = new ActionScheduler_DBLogger();
|
||||
|
||||
$config = new Config();
|
||||
$config->set_source_store( $source_store );
|
||||
$config->set_source_logger( $source_logger );
|
||||
$config->set_destination_store( $destination_store );
|
||||
$config->set_destination_logger( $destination_logger );
|
||||
|
||||
$hybrid_store = new ActionScheduler_HybridStore( $config );
|
||||
|
||||
$source_actions = array();
|
||||
$destination_actions = array();
|
||||
|
||||
for ( $i = 0; $i < 2; $i++ ) {
|
||||
// create in instance in the source store.
|
||||
$time = as_get_datetime_object( ( $i * 10 + 1 ) . ' minutes ago' );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$action = new ActionScheduler_Action( __FUNCTION__, array(), $schedule );
|
||||
|
||||
$source_actions[] = $source_store->save_action( $action );
|
||||
|
||||
// create an instance in the destination store.
|
||||
$time = as_get_datetime_object( ( $i * 10 + 5 ) . ' minutes ago' );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$action = new ActionScheduler_Action( __FUNCTION__, array(), $schedule );
|
||||
|
||||
$destination_actions[] = $destination_store->save_action( $action );
|
||||
}
|
||||
|
||||
foreach ( $source_actions as $action_id ) {
|
||||
$hybrid_store->mark_complete( $action_id );
|
||||
$action = $hybrid_store->fetch_action( $action_id );
|
||||
$this->assertInstanceOf( ActionScheduler_FinishedAction::class, $action );
|
||||
}
|
||||
|
||||
foreach ( $destination_actions as $action_id ) {
|
||||
$hybrid_store->mark_complete( $action_id );
|
||||
$action = $hybrid_store->fetch_action( $action_id );
|
||||
$this->assertInstanceOf( ActionScheduler_FinishedAction::class, $action );
|
||||
}
|
||||
}
|
||||
}
|
||||
474
tests/phpunit/jobstore/ActionScheduler_wpPostStore_Test.php
Normal file
474
tests/phpunit/jobstore/ActionScheduler_wpPostStore_Test.php
Normal file
@@ -0,0 +1,474 @@
|
||||
<?php
|
||||
|
||||
use Action_Scheduler\Tests\DataStores\AbstractStoreTest;
|
||||
|
||||
/**
|
||||
* Class ActionScheduler_wpPostStore_Test
|
||||
* @group stores
|
||||
*/
|
||||
class ActionScheduler_wpPostStore_Test extends AbstractStoreTest {
|
||||
|
||||
/**
|
||||
* Get data store for tests.
|
||||
*
|
||||
* @return ActionScheduler_wpPostStore
|
||||
*/
|
||||
protected function get_store() {
|
||||
return new ActionScheduler_wpPostStore();
|
||||
}
|
||||
|
||||
public function test_create_action() {
|
||||
$time = as_get_datetime_object();
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$action = new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array(), $schedule );
|
||||
$store = new ActionScheduler_wpPostStore();
|
||||
$action_id = $store->save_action( $action );
|
||||
|
||||
$this->assertNotEmpty( $action_id );
|
||||
}
|
||||
|
||||
public function test_create_action_with_scheduled_date() {
|
||||
$time = as_get_datetime_object( strtotime( '-1 week' ) );
|
||||
$action = new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array(), new ActionScheduler_SimpleSchedule( $time ) );
|
||||
$store = new ActionScheduler_wpPostStore();
|
||||
|
||||
$action_id = $store->save_action( $action, $time );
|
||||
$action_date = $store->get_date( $action_id );
|
||||
|
||||
$this->assertEquals( $time->getTimestamp(), $action_date->getTimestamp() );
|
||||
}
|
||||
|
||||
public function test_retrieve_action() {
|
||||
$time = as_get_datetime_object();
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$action = new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array(), $schedule, 'my_group' );
|
||||
$store = new ActionScheduler_wpPostStore();
|
||||
$action_id = $store->save_action( $action );
|
||||
$retrieved = $store->fetch_action( $action_id );
|
||||
|
||||
$this->assertEquals( $action->get_hook(), $retrieved->get_hook() );
|
||||
$this->assertEqualSets( $action->get_args(), $retrieved->get_args() );
|
||||
$this->assertEquals( $action->get_schedule()->get_date()->getTimestamp(), $retrieved->get_schedule()->get_date()->getTimestamp() );
|
||||
$this->assertEquals( $action->get_group(), $retrieved->get_group() );
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provide_bad_args
|
||||
*
|
||||
* @param string $content Post content.
|
||||
*/
|
||||
public function test_action_bad_args( $content ) {
|
||||
$store = new ActionScheduler_wpPostStore();
|
||||
$post_id = wp_insert_post(
|
||||
array(
|
||||
'post_type' => ActionScheduler_wpPostStore::POST_TYPE,
|
||||
'post_status' => ActionScheduler_Store::STATUS_PENDING,
|
||||
'post_content' => $content,
|
||||
)
|
||||
);
|
||||
|
||||
$fetched = $store->fetch_action( $post_id );
|
||||
$this->assertInstanceOf( 'ActionScheduler_NullSchedule', $fetched->get_schedule() );
|
||||
}
|
||||
|
||||
public function provide_bad_args() {
|
||||
return array(
|
||||
array( '{"bad_json":true}}' ),
|
||||
);
|
||||
}
|
||||
|
||||
public function test_cancel_action() {
|
||||
$time = as_get_datetime_object();
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$action = new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array(), $schedule, 'my_group' );
|
||||
$store = new ActionScheduler_wpPostStore();
|
||||
$action_id = $store->save_action( $action );
|
||||
$store->cancel_action( $action_id );
|
||||
|
||||
$fetched = $store->fetch_action( $action_id );
|
||||
$this->assertInstanceOf( 'ActionScheduler_CanceledAction', $fetched );
|
||||
}
|
||||
|
||||
public function test_cancel_actions_by_hook() {
|
||||
$store = new ActionScheduler_wpPostStore();
|
||||
$actions = array();
|
||||
$hook = 'by_hook_test';
|
||||
for ( $day = 1; $day <= 3; $day++ ) {
|
||||
$delta = sprintf( '+%d day', $day );
|
||||
$time = as_get_datetime_object( $delta );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$action = new ActionScheduler_Action( $hook, array(), $schedule, 'my_group' );
|
||||
$actions[] = $store->save_action( $action );
|
||||
}
|
||||
$store->cancel_actions_by_hook( $hook );
|
||||
|
||||
foreach ( $actions as $action_id ) {
|
||||
$fetched = $store->fetch_action( $action_id );
|
||||
$this->assertInstanceOf( 'ActionScheduler_CanceledAction', $fetched );
|
||||
}
|
||||
}
|
||||
|
||||
public function test_cancel_actions_by_group() {
|
||||
$store = new ActionScheduler_wpPostStore();
|
||||
$actions = array();
|
||||
$group = 'by_group_test';
|
||||
|
||||
for ( $day = 1; $day <= 3; $day++ ) {
|
||||
$delta = sprintf( '+%d day', $day );
|
||||
$time = as_get_datetime_object( $delta );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$action = new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array(), $schedule, $group );
|
||||
$actions[] = $store->save_action( $action );
|
||||
}
|
||||
$store->cancel_actions_by_group( $group );
|
||||
|
||||
foreach ( $actions as $action_id ) {
|
||||
$fetched = $store->fetch_action( $action_id );
|
||||
$this->assertInstanceOf( 'ActionScheduler_CanceledAction', $fetched );
|
||||
}
|
||||
}
|
||||
|
||||
public function test_claim_actions() {
|
||||
$created_actions = array();
|
||||
$store = new ActionScheduler_wpPostStore();
|
||||
|
||||
for ( $i = 3; $i > -3; $i-- ) {
|
||||
$time = as_get_datetime_object( $i . ' hours' );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$action = new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array( $i ), $schedule, 'my_group' );
|
||||
|
||||
$created_actions[] = $store->save_action( $action );
|
||||
}
|
||||
|
||||
$claim = $store->stake_claim();
|
||||
$this->assertInstanceof( 'ActionScheduler_ActionClaim', $claim );
|
||||
|
||||
$this->assertCount( 3, $claim->get_actions() );
|
||||
$this->assertEqualSets( array_slice( $created_actions, 3, 3 ), $claim->get_actions() );
|
||||
}
|
||||
|
||||
public function test_claim_actions_order() {
|
||||
$store = new ActionScheduler_wpPostStore();
|
||||
$schedule = new ActionScheduler_SimpleSchedule( as_get_datetime_object( '-1 hour' ) );
|
||||
$created_actions = array(
|
||||
$store->save_action( new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array( 1 ), $schedule, 'my_group' ) ),
|
||||
$store->save_action( new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array( 1 ), $schedule, 'my_group' ) ),
|
||||
);
|
||||
|
||||
$claim = $store->stake_claim();
|
||||
$this->assertInstanceof( 'ActionScheduler_ActionClaim', $claim );
|
||||
|
||||
// Verify uniqueness of action IDs.
|
||||
$this->assertEquals( 2, count( array_unique( $created_actions ) ) );
|
||||
|
||||
// Verify the count and order of the actions.
|
||||
$claimed_actions = $claim->get_actions();
|
||||
$this->assertCount( 2, $claimed_actions );
|
||||
$this->assertEquals( $created_actions, $claimed_actions );
|
||||
|
||||
// Verify the reversed order doesn't pass.
|
||||
$reversed_actions = array_reverse( $created_actions );
|
||||
$this->assertNotEquals( $reversed_actions, $claimed_actions );
|
||||
}
|
||||
|
||||
public function test_duplicate_claim() {
|
||||
$created_actions = array();
|
||||
$store = new ActionScheduler_wpPostStore();
|
||||
|
||||
for ( $i = 0; $i > -3; $i-- ) {
|
||||
$time = as_get_datetime_object( $i . ' hours' );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$action = new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array( $i ), $schedule, 'my_group' );
|
||||
|
||||
$created_actions[] = $store->save_action( $action );
|
||||
}
|
||||
|
||||
$claim1 = $store->stake_claim();
|
||||
$claim2 = $store->stake_claim();
|
||||
$this->assertCount( 3, $claim1->get_actions() );
|
||||
$this->assertCount( 0, $claim2->get_actions() );
|
||||
}
|
||||
|
||||
public function test_release_claim() {
|
||||
$created_actions = array();
|
||||
$store = new ActionScheduler_wpPostStore();
|
||||
|
||||
for ( $i = 0; $i > -3; $i-- ) {
|
||||
$time = as_get_datetime_object( $i . ' hours' );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$action = new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array( $i ), $schedule, 'my_group' );
|
||||
|
||||
$created_actions[] = $store->save_action( $action );
|
||||
}
|
||||
|
||||
$claim1 = $store->stake_claim();
|
||||
|
||||
$store->release_claim( $claim1 );
|
||||
|
||||
$claim2 = $store->stake_claim();
|
||||
$this->assertCount( 3, $claim2->get_actions() );
|
||||
}
|
||||
|
||||
public function test_search() {
|
||||
$created_actions = array();
|
||||
$store = new ActionScheduler_wpPostStore();
|
||||
|
||||
for ( $i = -3; $i <= 3; $i++ ) {
|
||||
$time = as_get_datetime_object( $i . ' hours' );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$action = new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array( $i ), $schedule, 'my_group' );
|
||||
|
||||
$created_actions[] = $store->save_action( $action );
|
||||
}
|
||||
|
||||
$next_no_args = $store->find_action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK );
|
||||
$this->assertEquals( $created_actions[0], $next_no_args );
|
||||
|
||||
$next_with_args = $store->find_action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array( 'args' => array( 1 ) ) );
|
||||
$this->assertEquals( $created_actions[4], $next_with_args );
|
||||
|
||||
$non_existent = $store->find_action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array( 'args' => array( 17 ) ) );
|
||||
$this->assertNull( $non_existent );
|
||||
}
|
||||
|
||||
public function test_search_by_group() {
|
||||
$store = new ActionScheduler_wpPostStore();
|
||||
$schedule = new ActionScheduler_SimpleSchedule( as_get_datetime_object( 'tomorrow' ) );
|
||||
|
||||
$abc = $store->save_action( new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array( 1 ), $schedule, 'abc' ) );
|
||||
$def = $store->save_action( new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array( 1 ), $schedule, 'def' ) );
|
||||
$ghi = $store->save_action( new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array( 1 ), $schedule, 'ghi' ) );
|
||||
|
||||
$this->assertEquals( $abc, $store->find_action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array( 'group' => 'abc' ) ) );
|
||||
$this->assertEquals( $def, $store->find_action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array( 'group' => 'def' ) ) );
|
||||
$this->assertEquals( $ghi, $store->find_action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array( 'group' => 'ghi' ) ) );
|
||||
}
|
||||
|
||||
public function test_post_author() {
|
||||
$current_user = get_current_user_id();
|
||||
|
||||
$time = as_get_datetime_object();
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$action = new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array(), $schedule );
|
||||
$store = new ActionScheduler_wpPostStore();
|
||||
$action_id = $store->save_action( $action );
|
||||
|
||||
$post = get_post( $action_id );
|
||||
$this->assertEquals( 0, $post->post_author );
|
||||
|
||||
$new_user = $this->factory->user->create_object(
|
||||
array(
|
||||
'user_login' => __FUNCTION__,
|
||||
'user_pass' => md5( wp_rand() ),
|
||||
)
|
||||
);
|
||||
|
||||
wp_set_current_user( $new_user );
|
||||
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$action = new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array(), $schedule );
|
||||
$action_id = $store->save_action( $action );
|
||||
$post = get_post( $action_id );
|
||||
$this->assertEquals( 0, $post->post_author );
|
||||
|
||||
wp_set_current_user( $current_user );
|
||||
}
|
||||
|
||||
/**
|
||||
* @issue 13
|
||||
*/
|
||||
public function test_post_status_for_recurring_action() {
|
||||
$time = as_get_datetime_object( '10 minutes' );
|
||||
$schedule = new ActionScheduler_IntervalSchedule( $time, HOUR_IN_SECONDS );
|
||||
$action = new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array(), $schedule );
|
||||
$store = new ActionScheduler_wpPostStore();
|
||||
$action_id = $store->save_action( $action );
|
||||
|
||||
$action = $store->fetch_action( $action_id );
|
||||
$action->execute();
|
||||
$store->mark_complete( $action_id );
|
||||
|
||||
$next = $action->get_schedule()->get_next( as_get_datetime_object() );
|
||||
$new_action_id = $store->save_action( $action, $next );
|
||||
|
||||
$this->assertEquals( 'publish', get_post_status( $action_id ) );
|
||||
$this->assertEquals( 'pending', get_post_status( $new_action_id ) );
|
||||
}
|
||||
|
||||
public function test_get_run_date() {
|
||||
$time = as_get_datetime_object( '-10 minutes' );
|
||||
$schedule = new ActionScheduler_IntervalSchedule( $time, HOUR_IN_SECONDS );
|
||||
$action = new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array(), $schedule );
|
||||
$store = new ActionScheduler_wpPostStore();
|
||||
$action_id = $store->save_action( $action );
|
||||
|
||||
$this->assertEquals( $store->get_date( $action_id )->getTimestamp(), $time->getTimestamp() );
|
||||
|
||||
$action = $store->fetch_action( $action_id );
|
||||
$action->execute();
|
||||
$now = as_get_datetime_object();
|
||||
$store->mark_complete( $action_id );
|
||||
|
||||
$this->assertEquals( $store->get_date( $action_id )->getTimestamp(), $now->getTimestamp(), '', 1 ); // allow timestamp to be 1 second off for older versions of PHP.
|
||||
|
||||
$next = $action->get_schedule()->get_next( $now );
|
||||
$new_action_id = $store->save_action( $action, $next );
|
||||
|
||||
$this->assertEquals( (int) ( $now->getTimestamp() ) + HOUR_IN_SECONDS, $store->get_date( $new_action_id )->getTimestamp() );
|
||||
}
|
||||
|
||||
public function test_claim_actions_by_hooks() {
|
||||
$hook1 = __FUNCTION__ . '_hook_1';
|
||||
$hook2 = __FUNCTION__ . '_hook_2';
|
||||
$store = new ActionScheduler_wpPostStore();
|
||||
$schedule = new ActionScheduler_SimpleSchedule( as_get_datetime_object( '-1 hour' ) );
|
||||
|
||||
$action1 = $store->save_action( new ActionScheduler_Action( $hook1, array(), $schedule ) );
|
||||
$action2 = $store->save_action( new ActionScheduler_Action( $hook2, array(), $schedule ) );
|
||||
|
||||
// Claiming no hooks should include all actions.
|
||||
$claim = $store->stake_claim( 10 );
|
||||
$this->assertEquals( 2, count( $claim->get_actions() ) );
|
||||
$this->assertTrue( in_array( $action1, $claim->get_actions(), true ) );
|
||||
$this->assertTrue( in_array( $action2, $claim->get_actions(), true ) );
|
||||
$store->release_claim( $claim );
|
||||
|
||||
// Claiming a hook should claim only actions with that hook.
|
||||
$claim = $store->stake_claim( 10, null, array( $hook1 ) );
|
||||
$this->assertEquals( 1, count( $claim->get_actions() ) );
|
||||
$this->assertTrue( in_array( $action1, $claim->get_actions(), true ) );
|
||||
$store->release_claim( $claim );
|
||||
|
||||
// Claiming two hooks should claim actions with either of those hooks.
|
||||
$claim = $store->stake_claim( 10, null, array( $hook1, $hook2 ) );
|
||||
$this->assertEquals( 2, count( $claim->get_actions() ) );
|
||||
$this->assertTrue( in_array( $action1, $claim->get_actions(), true ) );
|
||||
$this->assertTrue( in_array( $action2, $claim->get_actions(), true ) );
|
||||
$store->release_claim( $claim );
|
||||
|
||||
// Claiming two hooks should claim actions with either of those hooks.
|
||||
$claim = $store->stake_claim( 10, null, array( __METHOD__ . '_hook_3' ) );
|
||||
$this->assertEquals( 0, count( $claim->get_actions() ) );
|
||||
$this->assertFalse( in_array( $action1, $claim->get_actions(), true ) );
|
||||
$this->assertFalse( in_array( $action2, $claim->get_actions(), true ) );
|
||||
$store->release_claim( $claim );
|
||||
}
|
||||
|
||||
/**
|
||||
* @issue 121
|
||||
*/
|
||||
public function test_claim_actions_by_group() {
|
||||
$group1 = md5( wp_rand() );
|
||||
$store = new ActionScheduler_wpPostStore();
|
||||
$schedule = new ActionScheduler_SimpleSchedule( as_get_datetime_object( '-1 hour' ) );
|
||||
|
||||
$action1 = $store->save_action( new ActionScheduler_Action( __METHOD__, array(), $schedule, $group1 ) );
|
||||
$action2 = $store->save_action( new ActionScheduler_Action( __METHOD__, array(), $schedule ) );
|
||||
|
||||
// Claiming no group should include all actions.
|
||||
$claim = $store->stake_claim( 10 );
|
||||
$this->assertEquals( 2, count( $claim->get_actions() ) );
|
||||
$this->assertTrue( in_array( $action1, $claim->get_actions(), true ) );
|
||||
$this->assertTrue( in_array( $action2, $claim->get_actions(), true ) );
|
||||
$store->release_claim( $claim );
|
||||
|
||||
// Claiming a group should claim only actions in that group.
|
||||
$claim = $store->stake_claim( 10, null, array(), $group1 );
|
||||
$this->assertEquals( 1, count( $claim->get_actions() ) );
|
||||
$this->assertTrue( in_array( $action1, $claim->get_actions(), true ) );
|
||||
$store->release_claim( $claim );
|
||||
}
|
||||
|
||||
public function test_claim_actions_by_hook_and_group() {
|
||||
$hook1 = __FUNCTION__ . '_hook_1';
|
||||
$hook2 = __FUNCTION__ . '_hook_2';
|
||||
$hook3 = __FUNCTION__ . '_hook_3';
|
||||
$group1 = 'group_' . md5( wp_rand() );
|
||||
$group2 = 'group_' . md5( wp_rand() );
|
||||
$store = new ActionScheduler_wpPostStore();
|
||||
$schedule = new ActionScheduler_SimpleSchedule( as_get_datetime_object( '-1 hour' ) );
|
||||
|
||||
$action1 = $store->save_action( new ActionScheduler_Action( $hook1, array(), $schedule, $group1 ) );
|
||||
$action2 = $store->save_action( new ActionScheduler_Action( $hook2, array(), $schedule ) );
|
||||
$action3 = $store->save_action( new ActionScheduler_Action( $hook3, array(), $schedule, $group2 ) );
|
||||
|
||||
// Claiming no hooks or group should include all actions.
|
||||
$claim = $store->stake_claim( 10 );
|
||||
$this->assertEquals( 3, count( $claim->get_actions() ) );
|
||||
$this->assertTrue( in_array( $action1, $claim->get_actions(), true ) );
|
||||
$this->assertTrue( in_array( $action2, $claim->get_actions(), true ) );
|
||||
$store->release_claim( $claim );
|
||||
|
||||
// Claiming a group and hook should claim only actions in that group.
|
||||
$claim = $store->stake_claim( 10, null, array( $hook1 ), $group1 );
|
||||
$this->assertEquals( 1, count( $claim->get_actions() ) );
|
||||
$this->assertTrue( in_array( $action1, $claim->get_actions(), true ) );
|
||||
$store->release_claim( $claim );
|
||||
|
||||
// Claiming a group and hook should claim only actions with that hook in that group.
|
||||
$claim = $store->stake_claim( 10, null, array( $hook2 ), $group1 );
|
||||
$this->assertEquals( 0, count( $claim->get_actions() ) );
|
||||
$this->assertFalse( in_array( $action1, $claim->get_actions(), true ) );
|
||||
$this->assertFalse( in_array( $action2, $claim->get_actions(), true ) );
|
||||
$store->release_claim( $claim );
|
||||
|
||||
// Claiming a group and hook should claim only actions with that hook in that group.
|
||||
$claim = $store->stake_claim( 10, null, array( $hook1, $hook2 ), $group2 );
|
||||
$this->assertEquals( 0, count( $claim->get_actions() ) );
|
||||
$this->assertFalse( in_array( $action1, $claim->get_actions(), true ) );
|
||||
$this->assertFalse( in_array( $action2, $claim->get_actions(), true ) );
|
||||
$store->release_claim( $claim );
|
||||
}
|
||||
|
||||
/**
|
||||
* The query used to claim actions explicitly ignores future pending actions, but it
|
||||
* is still possible under unusual conditions (such as if MySQL runs out of temporary
|
||||
* storage space) for such actions to be returned.
|
||||
*
|
||||
* When this happens, we still expect the store to filter them out, otherwise there is
|
||||
* a risk that actions will be unexpectedly processed ahead of time.
|
||||
*
|
||||
* @see https://github.com/woocommerce/action-scheduler/issues/634
|
||||
*/
|
||||
public function test_claim_filters_out_unexpected_future_actions() {
|
||||
$group = __METHOD__;
|
||||
$store = new ActionScheduler_wpPostStore();
|
||||
|
||||
// Create 4 actions: 2 that are already due (-3hrs and -1hrs) and 2 that are not yet due (+1hr and +3hrs).
|
||||
for ( $i = -3; $i <= 3; $i += 2 ) {
|
||||
$schedule = new ActionScheduler_SimpleSchedule( as_get_datetime_object( $i . ' hours' ) );
|
||||
$action_ids[] = $store->save_action( new ActionScheduler_Action( 'test_' . $i, array(), $schedule, $group ) );
|
||||
}
|
||||
|
||||
// This callback is used to simulate the unusual conditions whereby MySQL might unexpectedly return future
|
||||
// actions, contrary to the conditions used by the store object when staking its claim.
|
||||
$simulate_unexpected_db_behavior = function( $sql ) use ( $action_ids ) {
|
||||
global $wpdb;
|
||||
|
||||
$post_type = ActionScheduler_wpPostStore::POST_TYPE;
|
||||
$pending = ActionScheduler_wpPostStore::STATUS_PENDING;
|
||||
|
||||
// Look out for the claim update query, ignore all others.
|
||||
if (
|
||||
0 !== strpos( $sql, "UPDATE $wpdb->posts" )
|
||||
|| 0 !== strpos( $sql, "WHERE post_type = '$post_type' AND post_status = '$pending' AND post_password = ''" )
|
||||
|| ! preg_match( "/AND post_date_gmt <= '([0-9:\-\s]{19})'/", $sql, $matches )
|
||||
|| count( $matches ) !== 2
|
||||
) {
|
||||
return $sql;
|
||||
}
|
||||
|
||||
// Now modify the query, forcing it to also return the future actions we created.
|
||||
return str_replace( $matches[1], as_get_datetime_object( '+4 hours' )->format( 'Y-m-d H:i:s' ), $sql );
|
||||
};
|
||||
|
||||
add_filter( 'query', $simulate_unexpected_db_behavior );
|
||||
$claim = $store->stake_claim( 10, null, array(), $group );
|
||||
$claimed_actions = $claim->get_actions();
|
||||
$this->assertCount( 2, $claimed_actions );
|
||||
|
||||
// Cleanup.
|
||||
remove_filter( 'query', $simulate_unexpected_db_behavior );
|
||||
$store->release_claim( $claim );
|
||||
}
|
||||
}
|
||||
77
tests/phpunit/lock/ActionScheduler_OptionLock_Test.php
Normal file
77
tests/phpunit/lock/ActionScheduler_OptionLock_Test.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class ActionScheduler_Lock_Test
|
||||
* @package test_cases\lock
|
||||
*/
|
||||
class ActionScheduler_OptionLock_Test extends ActionScheduler_UnitTestCase {
|
||||
public function test_instance() {
|
||||
$lock = ActionScheduler::lock();
|
||||
$this->assertInstanceOf( 'ActionScheduler_Lock', $lock );
|
||||
$this->assertInstanceOf( 'ActionScheduler_OptionLock', $lock );
|
||||
}
|
||||
|
||||
public function test_is_locked() {
|
||||
$lock = ActionScheduler::lock();
|
||||
$lock_type = md5( wp_rand() );
|
||||
|
||||
$this->assertFalse( $lock->is_locked( $lock_type ) );
|
||||
|
||||
$lock->set( $lock_type );
|
||||
$this->assertTrue( $lock->is_locked( $lock_type ) );
|
||||
}
|
||||
|
||||
public function test_set() {
|
||||
$lock = ActionScheduler::lock();
|
||||
$lock_type = md5( wp_rand() );
|
||||
|
||||
$lock->set( $lock_type );
|
||||
$this->assertTrue( $lock->is_locked( $lock_type ) );
|
||||
}
|
||||
|
||||
public function test_get_expiration() {
|
||||
$lock = ActionScheduler::lock();
|
||||
$lock_type = md5( wp_rand() );
|
||||
|
||||
$lock->set( $lock_type );
|
||||
|
||||
$expiration = $lock->get_expiration( $lock_type );
|
||||
$current_time = time();
|
||||
|
||||
$this->assertGreaterThanOrEqual( 0, $expiration );
|
||||
$this->assertGreaterThan( $current_time, $expiration );
|
||||
$this->assertLessThan( $current_time + MINUTE_IN_SECONDS + 1, $expiration );
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `ActionScheduler::lock()->set()` should fail if the lock is already held (ie, by another process).
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_lock_resists_race_conditions() {
|
||||
global $wpdb;
|
||||
|
||||
$lock = ActionScheduler::lock();
|
||||
$type = md5( wp_rand() );
|
||||
|
||||
// Approximate conditions in which a concurrently executing request manages to set (and obtain) the lock
|
||||
// immediately before the current request can do so.
|
||||
$simulate_concurrent_claim = function ( $query ) use ( $lock, $type ) {
|
||||
static $executed = false;
|
||||
|
||||
if ( ! $executed && false !== strpos( $query, 'action_scheduler_lock_' ) && 0 === strpos( $query, 'INSERT INTO' ) ) {
|
||||
$executed = true;
|
||||
$lock->set( $type );
|
||||
}
|
||||
|
||||
return $query;
|
||||
};
|
||||
|
||||
add_filter( 'query', $simulate_concurrent_claim );
|
||||
$wpdb->suppress_errors( true );
|
||||
$this->assertFalse( $lock->is_locked( $type ), 'Initially, the lock is not held' );
|
||||
$this->assertFalse( $lock->set( $type ), 'The lock was not obtained, because another process already claimed it.' );
|
||||
$wpdb->suppress_errors( false );
|
||||
remove_filter( 'query', $simulate_concurrent_claim );
|
||||
}
|
||||
}
|
||||
134
tests/phpunit/logging/ActionScheduler_DBLogger_Test.php
Normal file
134
tests/phpunit/logging/ActionScheduler_DBLogger_Test.php
Normal file
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class ActionScheduler_DBLogger_Test
|
||||
* @package test_cases\logging
|
||||
* @group tables
|
||||
*/
|
||||
class ActionScheduler_DBLogger_Test extends ActionScheduler_UnitTestCase {
|
||||
public function test_default_logger() {
|
||||
$logger = ActionScheduler::logger();
|
||||
$this->assertInstanceOf( 'ActionScheduler_Logger', $logger );
|
||||
$this->assertInstanceOf( ActionScheduler_DBLogger::class, $logger );
|
||||
}
|
||||
|
||||
public function test_add_log_entry() {
|
||||
$action_id = as_schedule_single_action( time(), __METHOD__ );
|
||||
$logger = ActionScheduler::logger();
|
||||
$message = 'Logging that something happened';
|
||||
$log_id = $logger->log( $action_id, $message );
|
||||
$entry = $logger->get_entry( $log_id );
|
||||
|
||||
$this->assertEquals( $action_id, $entry->get_action_id() );
|
||||
$this->assertEquals( $message, $entry->get_message() );
|
||||
}
|
||||
|
||||
public function test_storage_logs() {
|
||||
$action_id = as_schedule_single_action( time(), __METHOD__ );
|
||||
$logger = ActionScheduler::logger();
|
||||
$logs = $logger->get_logs( $action_id );
|
||||
$expected = new ActionScheduler_LogEntry( $action_id, 'action created' );
|
||||
$this->assertCount( 1, $logs );
|
||||
$this->assertEquals( $expected->get_action_id(), $logs[0]->get_action_id() );
|
||||
$this->assertEquals( $expected->get_message(), $logs[0]->get_message() );
|
||||
}
|
||||
|
||||
public function test_execution_logs() {
|
||||
$action_id = as_schedule_single_action( time(), ActionScheduler_Callbacks::HOOK_WITH_CALLBACK );
|
||||
$logger = ActionScheduler::logger();
|
||||
$started = new ActionScheduler_LogEntry( $action_id, 'action started via Unit Tests' );
|
||||
$finished = new ActionScheduler_LogEntry( $action_id, 'action complete via Unit Tests' );
|
||||
|
||||
$runner = ActionScheduler_Mocker::get_queue_runner();
|
||||
$runner->run( 'Unit Tests' );
|
||||
|
||||
// Expect 3 logs with the correct action ID.
|
||||
$logs = $logger->get_logs( $action_id );
|
||||
$this->assertCount( 3, $logs );
|
||||
foreach ( $logs as $log ) {
|
||||
$this->assertEquals( $action_id, $log->get_action_id() );
|
||||
}
|
||||
|
||||
// Expect created, then started, then completed.
|
||||
$this->assertEquals( 'action created', $logs[0]->get_message() );
|
||||
$this->assertEquals( $started->get_message(), $logs[1]->get_message() );
|
||||
$this->assertEquals( $finished->get_message(), $logs[2]->get_message() );
|
||||
}
|
||||
|
||||
public function test_failed_execution_logs() {
|
||||
$hook = __METHOD__;
|
||||
add_action( $hook, array( $this, 'a_hook_callback_that_throws_an_exception' ) );
|
||||
$action_id = as_schedule_single_action( time(), $hook );
|
||||
$logger = ActionScheduler::logger();
|
||||
$started = new ActionScheduler_LogEntry( $action_id, 'action started via Unit Tests' );
|
||||
$finished = new ActionScheduler_LogEntry( $action_id, 'action complete via Unit Tests' );
|
||||
$failed = new ActionScheduler_LogEntry( $action_id, 'action failed via Unit Tests: Execution failed' );
|
||||
|
||||
$runner = ActionScheduler_Mocker::get_queue_runner();
|
||||
$runner->run( 'Unit Tests' );
|
||||
|
||||
// Expect 3 logs with the correct action ID.
|
||||
$logs = $logger->get_logs( $action_id );
|
||||
$this->assertCount( 3, $logs );
|
||||
foreach ( $logs as $log ) {
|
||||
$this->assertEquals( $action_id, $log->get_action_id() );
|
||||
$this->assertNotEquals( $finished->get_message(), $log->get_message() );
|
||||
}
|
||||
|
||||
// Expect created, then started, then failed.
|
||||
$this->assertEquals( 'action created', $logs[0]->get_message() );
|
||||
$this->assertEquals( $started->get_message(), $logs[1]->get_message() );
|
||||
$this->assertEquals( $failed->get_message(), $logs[2]->get_message() );
|
||||
}
|
||||
|
||||
public function test_fatal_error_log() {
|
||||
$action_id = as_schedule_single_action( time(), __METHOD__ );
|
||||
$logger = ActionScheduler::logger();
|
||||
$args = array(
|
||||
'type' => E_ERROR,
|
||||
'message' => 'Test error',
|
||||
'file' => __FILE__,
|
||||
'line' => __LINE__,
|
||||
);
|
||||
|
||||
do_action( 'action_scheduler_unexpected_shutdown', $action_id, $args );
|
||||
|
||||
$logs = $logger->get_logs( $action_id );
|
||||
$found_log = false;
|
||||
foreach ( $logs as $l ) {
|
||||
if ( strpos( $l->get_message(), 'unexpected shutdown' ) === 0 ) {
|
||||
$found_log = true;
|
||||
}
|
||||
}
|
||||
$this->assertTrue( $found_log, 'Unexpected shutdown log not found' );
|
||||
}
|
||||
|
||||
public function test_canceled_action_log() {
|
||||
$action_id = as_schedule_single_action( time(), __METHOD__ );
|
||||
as_unschedule_action( __METHOD__ );
|
||||
$logger = ActionScheduler::logger();
|
||||
$logs = $logger->get_logs( $action_id );
|
||||
$expected = new ActionScheduler_LogEntry( $action_id, 'action canceled' );
|
||||
$this->assertEquals( $expected->get_message(), end( $logs )->get_message() );
|
||||
}
|
||||
|
||||
public function test_deleted_action_cleanup() {
|
||||
$time = as_get_datetime_object( '-10 minutes' );
|
||||
$schedule = new \ActionScheduler_SimpleSchedule( $time );
|
||||
$action = new \ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array(), $schedule );
|
||||
$store = new ActionScheduler_DBStore();
|
||||
$action_id = $store->save_action( $action );
|
||||
|
||||
$logger = new ActionScheduler_DBLogger();
|
||||
$logs = $logger->get_logs( $action_id );
|
||||
$this->assertNotEmpty( $logs );
|
||||
|
||||
$store->delete_action( $action_id );
|
||||
$logs = $logger->get_logs( $action_id );
|
||||
$this->assertEmpty( $logs );
|
||||
}
|
||||
|
||||
public function a_hook_callback_that_throws_an_exception() {
|
||||
throw new \RuntimeException( 'Execution failed' );
|
||||
}
|
||||
}
|
||||
241
tests/phpunit/logging/ActionScheduler_wpCommentLogger_Test.php
Normal file
241
tests/phpunit/logging/ActionScheduler_wpCommentLogger_Test.php
Normal file
@@ -0,0 +1,241 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class ActionScheduler_wpCommentLogger_Test
|
||||
* @package test_cases\logging
|
||||
*/
|
||||
class ActionScheduler_wpCommentLogger_Test extends ActionScheduler_UnitTestCase {
|
||||
|
||||
/** @var bool */
|
||||
private $use_comment_logger;
|
||||
|
||||
public function test_default_logger() {
|
||||
$logger = ActionScheduler::logger();
|
||||
$this->assertInstanceOf( 'ActionScheduler_Logger', $logger );
|
||||
if ( $this->using_comment_logger() ) {
|
||||
$this->assertInstanceOf( 'ActionScheduler_wpCommentLogger', $logger );
|
||||
} else {
|
||||
$this->assertNotInstanceOf( 'ActionScheduler_wpCommentLogger', $logger );
|
||||
}
|
||||
}
|
||||
|
||||
public function test_add_log_entry() {
|
||||
$action_id = as_schedule_single_action( time(), 'a hook' );
|
||||
$logger = ActionScheduler::logger();
|
||||
$message = 'Logging that something happened';
|
||||
$log_id = $logger->log( $action_id, $message );
|
||||
$entry = $logger->get_entry( $log_id );
|
||||
|
||||
$this->assertEquals( $action_id, $entry->get_action_id() );
|
||||
$this->assertEquals( $message, $entry->get_message() );
|
||||
}
|
||||
|
||||
public function test_add_log_datetime() {
|
||||
$action_id = as_schedule_single_action( time(), 'a hook' );
|
||||
$logger = ActionScheduler::logger();
|
||||
$message = 'Logging that something happened';
|
||||
$date = new DateTime( 'now', new DateTimeZone( 'UTC' ) );
|
||||
$log_id = $logger->log( $action_id, $message, $date );
|
||||
$entry = $logger->get_entry( $log_id );
|
||||
|
||||
$this->assertEquals( $action_id, $entry->get_action_id() );
|
||||
$this->assertEquals( $message, $entry->get_message() );
|
||||
|
||||
$date = new ActionScheduler_DateTime( 'now', new DateTimeZone( 'UTC' ) );
|
||||
$log_id = $logger->log( $action_id, $message, $date );
|
||||
$entry = $logger->get_entry( $log_id );
|
||||
|
||||
$this->assertEquals( $action_id, $entry->get_action_id() );
|
||||
$this->assertEquals( $message, $entry->get_message() );
|
||||
}
|
||||
|
||||
public function test_erroneous_entry_id() {
|
||||
$comment = wp_insert_comment(
|
||||
array(
|
||||
'comment_post_ID' => 1,
|
||||
'comment_author' => 'test',
|
||||
'comment_content' => 'this is not a log entry',
|
||||
)
|
||||
);
|
||||
|
||||
$logger = ActionScheduler::logger();
|
||||
$entry = $logger->get_entry( $comment );
|
||||
|
||||
$this->assertEquals( '', $entry->get_action_id() );
|
||||
$this->assertEquals( '', $entry->get_message() );
|
||||
}
|
||||
|
||||
public function test_storage_comments() {
|
||||
$action_id = as_schedule_single_action( time(), 'a hook' );
|
||||
$logger = ActionScheduler::logger();
|
||||
$logs = $logger->get_logs( $action_id );
|
||||
$expected = new ActionScheduler_LogEntry( $action_id, 'action created' );
|
||||
|
||||
// phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
|
||||
$this->assertTrue( in_array( $this->log_entry_to_array( $expected ), $this->log_entry_to_array( $logs ) ) );
|
||||
}
|
||||
|
||||
protected function log_entry_to_array( $logs ) {
|
||||
if ( $logs instanceof ActionScheduler_LogEntry ) {
|
||||
return array(
|
||||
'action_id' => $logs->get_action_id(),
|
||||
'message' => $logs->get_message(),
|
||||
);
|
||||
}
|
||||
|
||||
foreach ( $logs as $id => $log ) {
|
||||
$logs[ $id ] = array(
|
||||
'action_id' => $log->get_action_id(),
|
||||
'message' => $log->get_message(),
|
||||
);
|
||||
}
|
||||
|
||||
return $logs;
|
||||
}
|
||||
|
||||
public function test_execution_comments() {
|
||||
$action_id = as_schedule_single_action( time(), ActionScheduler_Callbacks::HOOK_WITH_CALLBACK );
|
||||
$logger = ActionScheduler::logger();
|
||||
$started = new ActionScheduler_LogEntry( $action_id, 'action started via Unit Tests' );
|
||||
$finished = new ActionScheduler_LogEntry( $action_id, 'action complete via Unit Tests' );
|
||||
|
||||
$runner = ActionScheduler_Mocker::get_queue_runner();
|
||||
$runner->run( 'Unit Tests' );
|
||||
|
||||
$logs = $logger->get_logs( $action_id );
|
||||
|
||||
// phpcs:disable WordPress.PHP.StrictInArray.MissingTrueStrict
|
||||
$this->assertTrue( in_array( $this->log_entry_to_array( $started ), $this->log_entry_to_array( $logs ) ) );
|
||||
$this->assertTrue( in_array( $this->log_entry_to_array( $finished ), $this->log_entry_to_array( $logs ) ) );
|
||||
// phpcs:enable
|
||||
}
|
||||
|
||||
public function test_failed_execution_comments() {
|
||||
$hook = md5( wp_rand() );
|
||||
add_action( $hook, array( $this, 'a_hook_callback_that_throws_an_exception' ) );
|
||||
|
||||
$action_id = as_schedule_single_action( time(), $hook );
|
||||
$logger = ActionScheduler::logger();
|
||||
$started = new ActionScheduler_LogEntry( $action_id, 'action started via Unit Tests' );
|
||||
$finished = new ActionScheduler_LogEntry( $action_id, 'action complete via Unit Tests' );
|
||||
$failed = new ActionScheduler_LogEntry( $action_id, 'action failed via Unit Tests: Execution failed' );
|
||||
|
||||
$runner = ActionScheduler_Mocker::get_queue_runner();
|
||||
$runner->run( 'Unit Tests' );
|
||||
|
||||
$logs = $logger->get_logs( $action_id );
|
||||
|
||||
// phpcs:disable WordPress.PHP.StrictInArray.MissingTrueStrict
|
||||
$this->assertTrue( in_array( $this->log_entry_to_array( $started ), $this->log_entry_to_array( $logs ) ) );
|
||||
$this->assertFalse( in_array( $this->log_entry_to_array( $finished ), $this->log_entry_to_array( $logs ) ) );
|
||||
$this->assertTrue( in_array( $this->log_entry_to_array( $failed ), $this->log_entry_to_array( $logs ) ) );
|
||||
// phpcs:enable
|
||||
}
|
||||
|
||||
public function test_failed_schedule_next_instance_comments() {
|
||||
$action_id = wp_rand();
|
||||
$logger = ActionScheduler::logger();
|
||||
$log_entry = new ActionScheduler_LogEntry( $action_id, 'There was a failure scheduling the next instance of this action: Execution failed' );
|
||||
|
||||
try {
|
||||
$this->a_hook_callback_that_throws_an_exception();
|
||||
} catch ( Exception $e ) {
|
||||
do_action( 'action_scheduler_failed_to_schedule_next_instance', $action_id, $e, new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK ) );
|
||||
}
|
||||
|
||||
$logs = $logger->get_logs( $action_id );
|
||||
|
||||
// phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
|
||||
$this->assertTrue( in_array( $this->log_entry_to_array( $log_entry ), $this->log_entry_to_array( $logs ) ) );
|
||||
}
|
||||
|
||||
public function test_fatal_error_comments() {
|
||||
$hook = md5( wp_rand() );
|
||||
$action_id = as_schedule_single_action( time(), $hook );
|
||||
$logger = ActionScheduler::logger();
|
||||
$args = array(
|
||||
'type' => E_ERROR,
|
||||
'message' => 'Test error',
|
||||
'file' => __FILE__,
|
||||
'line' => __LINE__,
|
||||
);
|
||||
|
||||
do_action( 'action_scheduler_unexpected_shutdown', $action_id, $args );
|
||||
|
||||
$logs = $logger->get_logs( $action_id );
|
||||
$found_log = false;
|
||||
foreach ( $logs as $l ) {
|
||||
if ( strpos( $l->get_message(), 'unexpected shutdown' ) === 0 ) {
|
||||
$found_log = true;
|
||||
}
|
||||
}
|
||||
$this->assertTrue( $found_log, 'Unexpected shutdown log not found' );
|
||||
}
|
||||
|
||||
public function test_canceled_action_comments() {
|
||||
$action_id = as_schedule_single_action( time(), 'a hook' );
|
||||
as_unschedule_action( 'a hook' );
|
||||
|
||||
$logger = ActionScheduler::logger();
|
||||
$logs = $logger->get_logs( $action_id );
|
||||
$expected = new ActionScheduler_LogEntry( $action_id, 'action canceled' );
|
||||
|
||||
// phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
|
||||
$this->assertTrue( in_array( $this->log_entry_to_array( $expected ), $this->log_entry_to_array( $logs ) ) );
|
||||
}
|
||||
|
||||
public function a_hook_callback_that_throws_an_exception() {
|
||||
throw new RuntimeException( 'Execution failed' );
|
||||
}
|
||||
|
||||
public function test_filtering_of_get_comments() {
|
||||
if ( ! $this->using_comment_logger() ) {
|
||||
$this->assertTrue( true );
|
||||
return;
|
||||
}
|
||||
|
||||
$post_id = $this->factory->post->create_object( array( 'post_title' => __FUNCTION__ ) );
|
||||
$comment_id = $this->factory->comment->create_object(
|
||||
array(
|
||||
'comment_post_ID' => $post_id,
|
||||
'comment_author' => __CLASS__,
|
||||
'comment_content' => __FUNCTION__,
|
||||
)
|
||||
);
|
||||
|
||||
// Verify that we're getting the expected comment before we add logging comments.
|
||||
$comments = get_comments();
|
||||
$this->assertCount( 1, $comments );
|
||||
$this->assertEquals( $comment_id, $comments[0]->comment_ID );
|
||||
|
||||
$action_id = as_schedule_single_action( time(), 'a hook' );
|
||||
$logger = ActionScheduler::logger();
|
||||
$message = 'Logging that something happened';
|
||||
$log_id = $logger->log( $action_id, $message );
|
||||
|
||||
// Verify that logging comments are excluded from general comment queries.
|
||||
$comments = get_comments();
|
||||
$this->assertCount( 1, $comments );
|
||||
$this->assertEquals( $comment_id, $comments[0]->comment_ID );
|
||||
|
||||
// Verify that logging comments are returned when asking for them specifically.
|
||||
$comments = get_comments(
|
||||
array(
|
||||
'type' => ActionScheduler_wpCommentLogger::TYPE,
|
||||
)
|
||||
);
|
||||
|
||||
// Expecting two: one when the action is created, another when we added our custom log.
|
||||
$this->assertCount( 2, $comments );
|
||||
$this->assertContains( $log_id, wp_list_pluck( $comments, 'comment_ID' ) );
|
||||
}
|
||||
|
||||
private function using_comment_logger() {
|
||||
if ( is_null( $this->use_comment_logger ) ) {
|
||||
$this->use_comment_logger = ! ActionScheduler_DataController::dependencies_met();
|
||||
}
|
||||
|
||||
return $this->use_comment_logger;
|
||||
}
|
||||
}
|
||||
|
||||
144
tests/phpunit/migration/ActionMigrator_Test.php
Normal file
144
tests/phpunit/migration/ActionMigrator_Test.php
Normal file
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
use Action_Scheduler\Migration\ActionMigrator;
|
||||
use Action_Scheduler\Migration\LogMigrator;
|
||||
|
||||
/**
|
||||
* Class ActionMigrator_Test
|
||||
* @group migration
|
||||
*/
|
||||
class ActionMigrator_Test extends ActionScheduler_UnitTestCase {
|
||||
public function setUp(): void {
|
||||
parent::setUp();
|
||||
if ( ! taxonomy_exists( ActionScheduler_wpPostStore::GROUP_TAXONOMY ) ) {
|
||||
// register the post type and taxonomy necessary for the store to work.
|
||||
$store = new ActionScheduler_wpPostStore();
|
||||
$store->init();
|
||||
}
|
||||
}
|
||||
|
||||
public function test_migrate_from_wpPost_to_db() {
|
||||
$source = new ActionScheduler_wpPostStore();
|
||||
$destination = new ActionScheduler_DBStore();
|
||||
$migrator = new ActionMigrator( $source, $destination, $this->get_log_migrator() );
|
||||
|
||||
$time = as_get_datetime_object();
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$action = new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array(), $schedule, 'my_group' );
|
||||
$action_id = $source->save_action( $action );
|
||||
|
||||
$new_id = $migrator->migrate( $action_id );
|
||||
|
||||
// ensure we get the same record out of the new store as we stored in the old.
|
||||
$retrieved = $destination->fetch_action( $new_id );
|
||||
$this->assertEquals( $action->get_hook(), $retrieved->get_hook() );
|
||||
$this->assertEqualSets( $action->get_args(), $retrieved->get_args() );
|
||||
$this->assertEquals( $action->get_schedule()->get_date()->format( 'U' ), $retrieved->get_schedule()->get_date()->format( 'U' ) );
|
||||
$this->assertEquals( $action->get_group(), $retrieved->get_group() );
|
||||
$this->assertEquals( \ActionScheduler_Store::STATUS_PENDING, $destination->get_status( $new_id ) );
|
||||
|
||||
// ensure that the record in the old store does not exist.
|
||||
$old_action = $source->fetch_action( $action_id );
|
||||
$this->assertInstanceOf( 'ActionScheduler_NullAction', $old_action );
|
||||
}
|
||||
|
||||
public function test_does_not_migrate_missing_action_from_wpPost_to_db() {
|
||||
$source = new ActionScheduler_wpPostStore();
|
||||
$destination = new ActionScheduler_DBStore();
|
||||
$migrator = new ActionMigrator( $source, $destination, $this->get_log_migrator() );
|
||||
|
||||
$action_id = wp_rand( 100, 100000 );
|
||||
|
||||
$new_id = $migrator->migrate( $action_id );
|
||||
$this->assertEquals( 0, $new_id );
|
||||
|
||||
// ensure we get the same record out of the new store as we stored in the old.
|
||||
$retrieved = $destination->fetch_action( $new_id );
|
||||
$this->assertInstanceOf( 'ActionScheduler_NullAction', $retrieved );
|
||||
}
|
||||
|
||||
public function test_migrate_completed_action_from_wpPost_to_db() {
|
||||
$source = new ActionScheduler_wpPostStore();
|
||||
$destination = new ActionScheduler_DBStore();
|
||||
$migrator = new ActionMigrator( $source, $destination, $this->get_log_migrator() );
|
||||
|
||||
$time = as_get_datetime_object();
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$action = new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array(), $schedule, 'my_group' );
|
||||
$action_id = $source->save_action( $action );
|
||||
$source->mark_complete( $action_id );
|
||||
|
||||
$new_id = $migrator->migrate( $action_id );
|
||||
|
||||
// ensure we get the same record out of the new store as we stored in the old.
|
||||
$retrieved = $destination->fetch_action( $new_id );
|
||||
$this->assertEquals( $action->get_hook(), $retrieved->get_hook() );
|
||||
$this->assertEqualSets( $action->get_args(), $retrieved->get_args() );
|
||||
$this->assertEquals( $action->get_schedule()->get_date()->format( 'U' ), $retrieved->get_schedule()->get_date()->format( 'U' ) );
|
||||
$this->assertEquals( $action->get_group(), $retrieved->get_group() );
|
||||
$this->assertTrue( $retrieved->is_finished() );
|
||||
$this->assertEquals( \ActionScheduler_Store::STATUS_COMPLETE, $destination->get_status( $new_id ) );
|
||||
|
||||
// ensure that the record in the old store does not exist.
|
||||
$old_action = $source->fetch_action( $action_id );
|
||||
$this->assertInstanceOf( 'ActionScheduler_NullAction', $old_action );
|
||||
}
|
||||
|
||||
public function test_migrate_failed_action_from_wpPost_to_db() {
|
||||
$source = new ActionScheduler_wpPostStore();
|
||||
$destination = new ActionScheduler_DBStore();
|
||||
$migrator = new ActionMigrator( $source, $destination, $this->get_log_migrator() );
|
||||
|
||||
$time = as_get_datetime_object();
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$action = new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array(), $schedule, 'my_group' );
|
||||
$action_id = $source->save_action( $action );
|
||||
$source->mark_failure( $action_id );
|
||||
|
||||
$new_id = $migrator->migrate( $action_id );
|
||||
|
||||
// ensure we get the same record out of the new store as we stored in the old.
|
||||
$retrieved = $destination->fetch_action( $new_id );
|
||||
$this->assertEquals( $action->get_hook(), $retrieved->get_hook() );
|
||||
$this->assertEqualSets( $action->get_args(), $retrieved->get_args() );
|
||||
$this->assertEquals( $action->get_schedule()->get_date()->format( 'U' ), $retrieved->get_schedule()->get_date()->format( 'U' ) );
|
||||
$this->assertEquals( $action->get_group(), $retrieved->get_group() );
|
||||
$this->assertTrue( $retrieved->is_finished() );
|
||||
$this->assertEquals( \ActionScheduler_Store::STATUS_FAILED, $destination->get_status( $new_id ) );
|
||||
|
||||
// ensure that the record in the old store does not exist.
|
||||
$old_action = $source->fetch_action( $action_id );
|
||||
$this->assertInstanceOf( 'ActionScheduler_NullAction', $old_action );
|
||||
}
|
||||
|
||||
public function test_migrate_canceled_action_from_wpPost_to_db() {
|
||||
$source = new ActionScheduler_wpPostStore();
|
||||
$destination = new ActionScheduler_DBStore();
|
||||
$migrator = new ActionMigrator( $source, $destination, $this->get_log_migrator() );
|
||||
|
||||
$time = as_get_datetime_object();
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$action = new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array(), $schedule, 'my_group' );
|
||||
$action_id = $source->save_action( $action );
|
||||
$source->cancel_action( $action_id );
|
||||
|
||||
$new_id = $migrator->migrate( $action_id );
|
||||
|
||||
// ensure we get the same record out of the new store as we stored in the old.
|
||||
$retrieved = $destination->fetch_action( $new_id );
|
||||
$this->assertEquals( $action->get_hook(), $retrieved->get_hook() );
|
||||
$this->assertEqualSets( $action->get_args(), $retrieved->get_args() );
|
||||
$this->assertEquals( $action->get_schedule()->get_date()->format( 'U' ), $retrieved->get_schedule()->get_date()->format( 'U' ) );
|
||||
$this->assertEquals( $action->get_group(), $retrieved->get_group() );
|
||||
$this->assertTrue( $retrieved->is_finished() );
|
||||
$this->assertEquals( \ActionScheduler_Store::STATUS_CANCELED, $destination->get_status( $new_id ) );
|
||||
|
||||
// ensure that the record in the old store does not exist.
|
||||
$old_action = $source->fetch_action( $action_id );
|
||||
$this->assertInstanceOf( 'ActionScheduler_NullAction', $old_action );
|
||||
}
|
||||
|
||||
private function get_log_migrator() {
|
||||
return new LogMigrator( \ActionScheduler::logger(), new ActionScheduler_DBLogger() );
|
||||
}
|
||||
}
|
||||
75
tests/phpunit/migration/BatchFetcher_Test.php
Normal file
75
tests/phpunit/migration/BatchFetcher_Test.php
Normal file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
use Action_Scheduler\Migration\BatchFetcher;
|
||||
use ActionScheduler_wpPostStore as PostStore;
|
||||
|
||||
/**
|
||||
* Class BatchFetcher_Test
|
||||
* @group migration
|
||||
*/
|
||||
class BatchFetcher_Test extends ActionScheduler_UnitTestCase {
|
||||
public function setUp(): void {
|
||||
parent::setUp();
|
||||
if ( ! taxonomy_exists( PostStore::GROUP_TAXONOMY ) ) {
|
||||
// register the post type and taxonomy necessary for the store to work.
|
||||
$store = new PostStore();
|
||||
$store->init();
|
||||
}
|
||||
}
|
||||
|
||||
public function test_nothing_to_migrate() {
|
||||
$store = new PostStore();
|
||||
$batch_fetcher = new BatchFetcher( $store );
|
||||
|
||||
$actions = $batch_fetcher->fetch();
|
||||
$this->assertEmpty( $actions );
|
||||
}
|
||||
|
||||
public function test_get_due_before_future() {
|
||||
$store = new PostStore();
|
||||
$due = array();
|
||||
$future = array();
|
||||
|
||||
for ( $i = 0; $i < 5; $i ++ ) {
|
||||
$time = as_get_datetime_object( $i + 1 . ' minutes' );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$action = new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array(), $schedule );
|
||||
$future[] = $store->save_action( $action );
|
||||
|
||||
$time = as_get_datetime_object( $i + 1 . ' minutes ago' );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$action = new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array(), $schedule );
|
||||
$due[] = $store->save_action( $action );
|
||||
}
|
||||
|
||||
$batch_fetcher = new BatchFetcher( $store );
|
||||
|
||||
$actions = $batch_fetcher->fetch();
|
||||
|
||||
$this->assertEqualSets( $due, $actions );
|
||||
}
|
||||
|
||||
public function test_get_future_before_complete() {
|
||||
$store = new PostStore();
|
||||
$future = array();
|
||||
$complete = array();
|
||||
|
||||
for ( $i = 0; $i < 5; $i ++ ) {
|
||||
$time = as_get_datetime_object( $i + 1 . ' minutes' );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$action = new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array(), $schedule );
|
||||
$future[] = $store->save_action( $action );
|
||||
|
||||
$time = as_get_datetime_object( $i + 1 . ' minutes ago' );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$action = new ActionScheduler_FinishedAction( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array(), $schedule );
|
||||
$complete[] = $store->save_action( $action );
|
||||
}
|
||||
|
||||
$batch_fetcher = new BatchFetcher( $store );
|
||||
|
||||
$actions = $batch_fetcher->fetch();
|
||||
|
||||
$this->assertEqualSets( $future, $actions );
|
||||
}
|
||||
}
|
||||
33
tests/phpunit/migration/Config_Test.php
Normal file
33
tests/phpunit/migration/Config_Test.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
use Action_Scheduler\Migration\Config;
|
||||
|
||||
/**
|
||||
* Class Config_Test
|
||||
* @group migration
|
||||
*/
|
||||
class Config_Test extends ActionScheduler_UnitTestCase {
|
||||
public function test_source_store_required() {
|
||||
$config = new Config();
|
||||
$this->expectException( \RuntimeException::class );
|
||||
$config->get_source_store();
|
||||
}
|
||||
|
||||
public function test_source_logger_required() {
|
||||
$config = new Config();
|
||||
$this->expectException( \RuntimeException::class );
|
||||
$config->get_source_logger();
|
||||
}
|
||||
|
||||
public function test_destination_store_required() {
|
||||
$config = new Config();
|
||||
$this->expectException( \RuntimeException::class );
|
||||
$config->get_destination_store();
|
||||
}
|
||||
|
||||
public function test_destination_logger_required() {
|
||||
$config = new Config();
|
||||
$this->expectException( \RuntimeException::class );
|
||||
$config->get_destination_logger();
|
||||
}
|
||||
}
|
||||
76
tests/phpunit/migration/Controller_Test.php
Normal file
76
tests/phpunit/migration/Controller_Test.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
/**
|
||||
* Contains tests for the Migration Controller.
|
||||
*
|
||||
* @package test_cases\migration
|
||||
*/
|
||||
|
||||
use ActionScheduler_StoreSchema as Schema;
|
||||
use Action_Scheduler\Migration\Controller;
|
||||
use Action_Scheduler\Migration\Scheduler;
|
||||
|
||||
/**
|
||||
* Test the migration controller.
|
||||
*
|
||||
* @group migration
|
||||
*/
|
||||
class Controller_Test extends ActionScheduler_UnitTestCase {
|
||||
/**
|
||||
* Test to ensure the Migration Controller will schedule the migration.
|
||||
*/
|
||||
public function test_schedules_migration() {
|
||||
as_unschedule_action( Scheduler::HOOK );
|
||||
Controller::instance()->schedule_migration();
|
||||
|
||||
$this->assertTrue(
|
||||
as_next_scheduled_action( Scheduler::HOOK ) > 0,
|
||||
'Confirm that the Migration Controller scheduled the migration.'
|
||||
);
|
||||
|
||||
as_unschedule_action( Scheduler::HOOK );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to ensure that if an essential table is missing, the Migration
|
||||
* Controller will not schedule a migration.
|
||||
*
|
||||
* @see https://github.com/woocommerce/action-scheduler/issues/653
|
||||
*/
|
||||
public function test_migration_not_scheduled_if_tables_are_missing() {
|
||||
as_unschedule_action( Scheduler::HOOK );
|
||||
$this->rename_claims_table();
|
||||
Controller::instance()->schedule_migration();
|
||||
|
||||
$this->assertFalse(
|
||||
as_next_scheduled_action( Scheduler::HOOK ),
|
||||
'When required tables are missing, the migration will not be scheduled.'
|
||||
);
|
||||
|
||||
$this->restore_claims_table_name();
|
||||
}
|
||||
|
||||
/**
|
||||
* Rename the claims table, so that it cannot be used by the library.
|
||||
*/
|
||||
private function rename_claims_table() {
|
||||
global $wpdb;
|
||||
$normal_table_name = $wpdb->prefix . Schema::CLAIMS_TABLE;
|
||||
$modified_table_name = $normal_table_name . 'x';
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$wpdb->query( "RENAME TABLE {$normal_table_name} TO {$modified_table_name}" );
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the expected name of the claims table, so that it can be used by the library
|
||||
* and any further tests.
|
||||
*/
|
||||
private function restore_claims_table_name() {
|
||||
global $wpdb;
|
||||
$normal_table_name = $wpdb->prefix . Schema::CLAIMS_TABLE;
|
||||
$modified_table_name = $normal_table_name . 'x';
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$wpdb->query( "RENAME TABLE {$modified_table_name} TO {$normal_table_name}" );
|
||||
}
|
||||
}
|
||||
52
tests/phpunit/migration/LogMigrator_Test.php
Normal file
52
tests/phpunit/migration/LogMigrator_Test.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
use Action_Scheduler\Migration\LogMigrator;
|
||||
|
||||
/**
|
||||
* Class LogMigrator_Test
|
||||
* @group migration
|
||||
*/
|
||||
class LogMigrator_Test extends ActionScheduler_UnitTestCase {
|
||||
public function setUp(): void {
|
||||
parent::setUp();
|
||||
if ( ! taxonomy_exists( ActionScheduler_wpPostStore::GROUP_TAXONOMY ) ) {
|
||||
// register the post type and taxonomy necessary for the store to work.
|
||||
$store = new ActionScheduler_wpPostStore();
|
||||
$store->init();
|
||||
}
|
||||
}
|
||||
|
||||
public function test_migrate_from_wpComment_to_db() {
|
||||
$source = new ActionScheduler_wpCommentLogger();
|
||||
$destination = new ActionScheduler_DBLogger();
|
||||
$migrator = new LogMigrator( $source, $destination );
|
||||
$source_action_id = wp_rand( 10, 10000 );
|
||||
$destination_action_id = wp_rand( 10, 10000 );
|
||||
|
||||
$logs = array();
|
||||
for ( $i = 0; $i < 3; $i++ ) {
|
||||
for ( $j = 0; $j < 5; $j++ ) {
|
||||
$logs[ $i ][ $j ] = md5( wp_rand() );
|
||||
if ( 1 === $i ) {
|
||||
$source->log( $source_action_id, $logs[ $i ][ $j ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$migrator->migrate( $source_action_id, $destination_action_id );
|
||||
|
||||
$migrated = $destination->get_logs( $destination_action_id );
|
||||
$this->assertEqualSets(
|
||||
$logs[1],
|
||||
array_map(
|
||||
function( $log ) {
|
||||
return $log->get_message();
|
||||
},
|
||||
$migrated
|
||||
)
|
||||
);
|
||||
|
||||
// no API for deleting logs, so we leave them for manual cleanup later.
|
||||
$this->assertCount( 5, $source->get_logs( $source_action_id ) );
|
||||
}
|
||||
}
|
||||
95
tests/phpunit/migration/Runner_Test.php
Normal file
95
tests/phpunit/migration/Runner_Test.php
Normal file
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
|
||||
use Action_Scheduler\Migration\Config;
|
||||
use Action_Scheduler\Migration\Runner;
|
||||
use ActionScheduler_wpCommentLogger as CommentLogger;
|
||||
use ActionScheduler_wpPostStore as PostStore;
|
||||
|
||||
/**
|
||||
* Class Runner_Test
|
||||
* @group migration
|
||||
*/
|
||||
class Runner_Test extends ActionScheduler_UnitTestCase {
|
||||
public function setUp(): void {
|
||||
parent::setUp();
|
||||
if ( ! taxonomy_exists( PostStore::GROUP_TAXONOMY ) ) {
|
||||
// register the post type and taxonomy necessary for the store to work.
|
||||
$store = new PostStore();
|
||||
$store->init();
|
||||
}
|
||||
}
|
||||
|
||||
public function test_migrate_batches() {
|
||||
$source_store = new PostStore();
|
||||
$destination_store = new ActionScheduler_DBStore();
|
||||
$source_logger = new CommentLogger();
|
||||
$destination_logger = new ActionScheduler_DBLogger();
|
||||
|
||||
$config = new Config();
|
||||
$config->set_source_store( $source_store );
|
||||
$config->set_source_logger( $source_logger );
|
||||
$config->set_destination_store( $destination_store );
|
||||
$config->set_destination_logger( $destination_logger );
|
||||
|
||||
$runner = new Runner( $config );
|
||||
|
||||
$due = array();
|
||||
$future = array();
|
||||
$complete = array();
|
||||
|
||||
for ( $i = 0; $i < 5; $i ++ ) {
|
||||
$time = as_get_datetime_object( $i + 1 . ' minutes' );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$action = new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array(), $schedule );
|
||||
$future[] = $source_store->save_action( $action );
|
||||
|
||||
$time = as_get_datetime_object( $i + 1 . ' minutes ago' );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$action = new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array(), $schedule );
|
||||
$due[] = $source_store->save_action( $action );
|
||||
|
||||
$time = as_get_datetime_object( $i + 1 . ' minutes ago' );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$action = new ActionScheduler_FinishedAction( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array(), $schedule );
|
||||
$complete[] = $source_store->save_action( $action );
|
||||
}
|
||||
|
||||
$created = $source_store->query_actions( array( 'per_page' => 0 ) );
|
||||
$this->assertCount( 15, $created );
|
||||
|
||||
$runner->run( 10 );
|
||||
|
||||
$args = array(
|
||||
'per_page' => 0,
|
||||
'hook' => ActionScheduler_Callbacks::HOOK_WITH_CALLBACK,
|
||||
);
|
||||
|
||||
// due actions should migrate in the first batch.
|
||||
$migrated = $destination_store->query_actions( $args );
|
||||
$this->assertCount( 5, $migrated );
|
||||
|
||||
$remaining = $source_store->query_actions( $args );
|
||||
$this->assertCount( 10, $remaining );
|
||||
|
||||
$runner->run( 10 );
|
||||
|
||||
// pending actions should migrate in the second batch.
|
||||
$migrated = $destination_store->query_actions( $args );
|
||||
$this->assertCount( 10, $migrated );
|
||||
|
||||
$remaining = $source_store->query_actions( $args );
|
||||
$this->assertCount( 5, $remaining );
|
||||
|
||||
$runner->run( 10 );
|
||||
|
||||
// completed actions should migrate in the third batch.
|
||||
$migrated = $destination_store->query_actions( $args );
|
||||
$this->assertCount( 15, $migrated );
|
||||
|
||||
$remaining = $source_store->query_actions( $args );
|
||||
$this->assertCount( 0, $remaining );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
138
tests/phpunit/migration/Scheduler_Test.php
Normal file
138
tests/phpunit/migration/Scheduler_Test.php
Normal file
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
use Action_Scheduler\Migration\Scheduler;
|
||||
use ActionScheduler_wpPostStore as PostStore;
|
||||
|
||||
/**
|
||||
* Class Scheduler_Test
|
||||
* @group migration
|
||||
*/
|
||||
class Scheduler_Test extends ActionScheduler_UnitTestCase {
|
||||
public function setUp(): void {
|
||||
parent::setUp();
|
||||
if ( ! taxonomy_exists( PostStore::GROUP_TAXONOMY ) ) {
|
||||
// register the post type and taxonomy necessary for the store to work.
|
||||
$store = new PostStore();
|
||||
$store->init();
|
||||
}
|
||||
}
|
||||
|
||||
public function test_migration_is_complete() {
|
||||
ActionScheduler_DataController::mark_migration_complete();
|
||||
$this->assertTrue( ActionScheduler_DataController::is_migration_complete() );
|
||||
}
|
||||
|
||||
public function test_migration_is_not_complete() {
|
||||
$this->assertFalse( ActionScheduler_DataController::is_migration_complete() );
|
||||
update_option( ActionScheduler_DataController::STATUS_FLAG, 'something_random' );
|
||||
$this->assertFalse( ActionScheduler_DataController::is_migration_complete() );
|
||||
}
|
||||
|
||||
public function test_migration_is_scheduled() {
|
||||
// Clear the any existing migration hooks that have already been setup.
|
||||
as_unschedule_all_actions( Scheduler::HOOK );
|
||||
|
||||
$scheduler = new Scheduler();
|
||||
$this->assertFalse(
|
||||
$scheduler->is_migration_scheduled(),
|
||||
'Migration is not automatically scheduled when a new ' . Scheduler::class . ' instance is created.'
|
||||
);
|
||||
|
||||
$scheduler->schedule_migration();
|
||||
$this->assertTrue(
|
||||
$scheduler->is_migration_scheduled(),
|
||||
'Migration is scheduled only after schedule_migration() has been called.'
|
||||
);
|
||||
}
|
||||
|
||||
public function test_scheduler_runs_migration() {
|
||||
$source_store = new PostStore();
|
||||
$destination_store = new ActionScheduler_DBStore();
|
||||
|
||||
$return_5 = function () {
|
||||
return 5;
|
||||
};
|
||||
add_filter( 'action_scheduler/migration_batch_size', $return_5 );
|
||||
|
||||
// Make sure successive migration actions are delayed so all actions aren't migrated at once on separate hooks.
|
||||
$return_60 = function () {
|
||||
return 60;
|
||||
};
|
||||
add_filter( 'action_scheduler/migration_interval', $return_60 );
|
||||
|
||||
for ( $i = 0; $i < 10; $i++ ) {
|
||||
$time = as_get_datetime_object( $i + 1 . ' minutes' );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$action = new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array(), $schedule );
|
||||
$future[] = $source_store->save_action( $action );
|
||||
|
||||
$time = as_get_datetime_object( $i + 1 . ' minutes ago' );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$action = new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array(), $schedule );
|
||||
$due[] = $source_store->save_action( $action );
|
||||
}
|
||||
|
||||
$this->assertCount( 20, $source_store->query_actions( array( 'per_page' => 0 ) ) );
|
||||
|
||||
$scheduler = new Scheduler();
|
||||
$scheduler->unschedule_migration();
|
||||
$scheduler->schedule_migration( time() - 1 );
|
||||
|
||||
$queue_runner = ActionScheduler_Mocker::get_queue_runner( $destination_store );
|
||||
$queue_runner->run();
|
||||
|
||||
// 5 actions should have moved from the source store when the queue runner triggered the migration action.
|
||||
$args = array(
|
||||
'per_page' => 0,
|
||||
'hook' => ActionScheduler_Callbacks::HOOK_WITH_CALLBACK,
|
||||
);
|
||||
$this->assertCount( 15, $source_store->query_actions( $args ) );
|
||||
|
||||
remove_filter( 'action_scheduler/migration_batch_size', $return_5 );
|
||||
remove_filter( 'action_scheduler/migration_interval', $return_60 );
|
||||
}
|
||||
|
||||
public function test_scheduler_marks_itself_complete() {
|
||||
$source_store = new PostStore();
|
||||
$destination_store = new ActionScheduler_DBStore();
|
||||
|
||||
for ( $i = 0; $i < 5; $i ++ ) {
|
||||
$time = as_get_datetime_object( $i + 1 . ' minutes ago' );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$action = new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array(), $schedule );
|
||||
$due[] = $source_store->save_action( $action );
|
||||
}
|
||||
|
||||
$this->assertCount( 5, $source_store->query_actions( array( 'per_page' => 0 ) ) );
|
||||
|
||||
$scheduler = new Scheduler();
|
||||
$scheduler->unschedule_migration();
|
||||
$scheduler->schedule_migration( time() - 1 );
|
||||
|
||||
$queue_runner = ActionScheduler_Mocker::get_queue_runner( $destination_store );
|
||||
$queue_runner->run();
|
||||
|
||||
// All actions should have moved from the source store when the queue runner triggered the migration action.
|
||||
$args = array(
|
||||
'per_page' => 0,
|
||||
'hook' => ActionScheduler_Callbacks::HOOK_WITH_CALLBACK,
|
||||
);
|
||||
$this->assertCount( 0, $source_store->query_actions( $args ) );
|
||||
|
||||
// schedule another so we can get it to run immediately.
|
||||
$scheduler->unschedule_migration();
|
||||
$scheduler->schedule_migration( time() - 1 );
|
||||
|
||||
// run again so it knows that there's nothing left to process.
|
||||
$queue_runner->run();
|
||||
|
||||
$scheduler->unhook();
|
||||
|
||||
// ensure the flag is set marking migration as complete.
|
||||
$this->assertTrue( ActionScheduler_DataController::is_migration_complete() );
|
||||
|
||||
// ensure that another instance has not been scheduled.
|
||||
$this->assertFalse( $scheduler->is_migration_scheduled() );
|
||||
|
||||
}
|
||||
}
|
||||
498
tests/phpunit/procedural_api/procedural_api_Test.php
Normal file
498
tests/phpunit/procedural_api/procedural_api_Test.php
Normal file
@@ -0,0 +1,498 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class procedural_api_Test
|
||||
*/
|
||||
class Procedural_API_Test extends ActionScheduler_UnitTestCase {
|
||||
|
||||
// phpcs:disable Squiz.Commenting.FunctionComment.Missing, Squiz.Commenting.FunctionComment.MissingParamTag
|
||||
public function test_schedule_action() {
|
||||
$time = time();
|
||||
$hook = md5( wp_rand() );
|
||||
$action_id = as_schedule_single_action( $time, $hook );
|
||||
|
||||
$store = ActionScheduler::store();
|
||||
$action = $store->fetch_action( $action_id );
|
||||
$this->assertEquals( $time, $action->get_schedule()->get_date()->getTimestamp() );
|
||||
$this->assertEquals( $hook, $action->get_hook() );
|
||||
}
|
||||
|
||||
public function test_recurring_action() {
|
||||
$time = time();
|
||||
$hook = md5( wp_rand() );
|
||||
$action_id = as_schedule_recurring_action( $time, HOUR_IN_SECONDS, $hook );
|
||||
|
||||
$store = ActionScheduler::store();
|
||||
$action = $store->fetch_action( $action_id );
|
||||
$this->assertEquals( $time, $action->get_schedule()->get_date()->getTimestamp() );
|
||||
$this->assertEquals( $time + HOUR_IN_SECONDS + 2, $action->get_schedule()->get_next( as_get_datetime_object( $time + 2 ) )->getTimestamp() );
|
||||
$this->assertEquals( $hook, $action->get_hook() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that we reject attempts to register a recurring action with an invalid interval. This guards against
|
||||
* 'runaway' recurring actions that are created accidentally and treated as having a zero-second interval.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_recurring_actions_reject_invalid_intervals() {
|
||||
$this->assertGreaterThan(
|
||||
0,
|
||||
as_schedule_recurring_action( time(), 1, 'foo' ),
|
||||
'When an integer is provided as the interval, a recurring action is successfully created.'
|
||||
);
|
||||
|
||||
$this->assertGreaterThan(
|
||||
0,
|
||||
as_schedule_recurring_action( time(), '10', 'foo' ),
|
||||
'When an integer-like string is provided as the interval, a recurring action is successfully created.'
|
||||
);
|
||||
|
||||
$this->assertGreaterThan(
|
||||
0,
|
||||
as_schedule_recurring_action( time(), 100.0, 'foo' ),
|
||||
'When an integer-value as a double is provided as the interval, a recurring action is successfully created.'
|
||||
);
|
||||
|
||||
$this->setExpectedIncorrectUsage( 'as_schedule_recurring_action' );
|
||||
$this->assertEquals(
|
||||
0,
|
||||
as_schedule_recurring_action( time(), 'nonsense', 'foo' ),
|
||||
'When a non-numeric string is provided as the interval, a recurring action is not created and a doing-it-wrong notice is emitted.'
|
||||
);
|
||||
|
||||
$this->setExpectedIncorrectUsage( 'as_schedule_recurring_action' );
|
||||
$this->assertEquals(
|
||||
0,
|
||||
as_schedule_recurring_action( time(), 123.456, 'foo' ),
|
||||
'When a non-integer double is provided as the interval, a recurring action is not created and a doing-it-wrong notice is emitted.'
|
||||
);
|
||||
}
|
||||
|
||||
public function test_cron_schedule() {
|
||||
$time = as_get_datetime_object( '2014-01-01' );
|
||||
$hook = md5( wp_rand() );
|
||||
$action_id = as_schedule_cron_action( $time->getTimestamp(), '0 0 10 10 *', $hook );
|
||||
|
||||
$store = ActionScheduler::store();
|
||||
$action = $store->fetch_action( $action_id );
|
||||
$expected_date = as_get_datetime_object( '2014-10-10' );
|
||||
$this->assertEquals( $expected_date->getTimestamp(), $action->get_schedule()->get_date()->getTimestamp() );
|
||||
$this->assertEquals( $hook, $action->get_hook() );
|
||||
|
||||
$expected_date = as_get_datetime_object( '2015-10-10' );
|
||||
$this->assertEquals( $expected_date->getTimestamp(), $action->get_schedule()->get_next( as_get_datetime_object( '2015-01-02' ) )->getTimestamp() );
|
||||
}
|
||||
|
||||
public function test_get_next() {
|
||||
$time = as_get_datetime_object( 'tomorrow' );
|
||||
$hook = md5( wp_rand() );
|
||||
as_schedule_recurring_action( $time->getTimestamp(), HOUR_IN_SECONDS, $hook );
|
||||
|
||||
$next = as_next_scheduled_action( $hook );
|
||||
|
||||
$this->assertEquals( $time->getTimestamp(), $next );
|
||||
}
|
||||
|
||||
public function test_get_next_async() {
|
||||
$hook = md5( wp_rand() );
|
||||
$action_id = as_enqueue_async_action( $hook );
|
||||
|
||||
$next = as_next_scheduled_action( $hook );
|
||||
|
||||
$this->assertTrue( $next );
|
||||
|
||||
$store = ActionScheduler::store();
|
||||
|
||||
// Completed async actions should still return false.
|
||||
$store->mark_complete( $action_id );
|
||||
$next = as_next_scheduled_action( $hook );
|
||||
$this->assertFalse( $next );
|
||||
|
||||
// Failed async actions should still return false.
|
||||
$store->mark_failure( $action_id );
|
||||
$next = as_next_scheduled_action( $hook );
|
||||
$this->assertFalse( $next );
|
||||
|
||||
// Cancelled async actions should still return false.
|
||||
$store->cancel_action( $action_id );
|
||||
$next = as_next_scheduled_action( $hook );
|
||||
$this->assertFalse( $next );
|
||||
}
|
||||
|
||||
public function provider_time_hook_args_group() {
|
||||
$time = time() + 60 * 2;
|
||||
$hook = md5( wp_rand() );
|
||||
$args = array( wp_rand(), wp_rand() );
|
||||
$group = 'test_group';
|
||||
|
||||
return array(
|
||||
|
||||
// Test with no args or group.
|
||||
array(
|
||||
'time' => $time,
|
||||
'hook' => $hook,
|
||||
'args' => array(),
|
||||
'group' => '',
|
||||
),
|
||||
|
||||
// Test with args but no group.
|
||||
array(
|
||||
'time' => $time,
|
||||
'hook' => $hook,
|
||||
'args' => $args,
|
||||
'group' => '',
|
||||
),
|
||||
|
||||
// Test with group but no args.
|
||||
array(
|
||||
'time' => $time,
|
||||
'hook' => $hook,
|
||||
'args' => array(),
|
||||
'group' => $group,
|
||||
),
|
||||
|
||||
// Test with args & group.
|
||||
array(
|
||||
'time' => $time,
|
||||
'hook' => $hook,
|
||||
'args' => $args,
|
||||
'group' => $group,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provider_time_hook_args_group
|
||||
*/
|
||||
public function test_unschedule( $time, $hook, $args, $group ) {
|
||||
|
||||
$action_id_unscheduled = as_schedule_single_action( $time, $hook, $args, $group );
|
||||
$action_scheduled_time = $time + 1;
|
||||
$action_id_scheduled = as_schedule_single_action( $action_scheduled_time, $hook, $args, $group );
|
||||
|
||||
as_unschedule_action( $hook, $args, $group );
|
||||
|
||||
$next = as_next_scheduled_action( $hook, $args, $group );
|
||||
$this->assertEquals( $action_scheduled_time, $next );
|
||||
|
||||
$store = ActionScheduler::store();
|
||||
$unscheduled_action = $store->fetch_action( $action_id_unscheduled );
|
||||
|
||||
// Make sure the next scheduled action is unscheduled.
|
||||
$this->assertEquals( $hook, $unscheduled_action->get_hook() );
|
||||
$this->assertEquals( as_get_datetime_object( $time ), $unscheduled_action->get_schedule()->get_date() );
|
||||
$this->assertEquals( ActionScheduler_Store::STATUS_CANCELED, $store->get_status( $action_id_unscheduled ) );
|
||||
$this->assertNull( $unscheduled_action->get_schedule()->get_next( as_get_datetime_object() ) );
|
||||
|
||||
// Make sure other scheduled actions are not unscheduled.
|
||||
$this->assertEquals( ActionScheduler_Store::STATUS_PENDING, $store->get_status( $action_id_scheduled ) );
|
||||
$scheduled_action = $store->fetch_action( $action_id_scheduled );
|
||||
|
||||
$this->assertEquals( $hook, $scheduled_action->get_hook() );
|
||||
$this->assertEquals( $action_scheduled_time, $scheduled_action->get_schedule()->get_date()->getTimestamp() );
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provider_time_hook_args_group
|
||||
*/
|
||||
public function test_unschedule_all( $time, $hook, $args, $group ) {
|
||||
|
||||
$hook = md5( $hook );
|
||||
$action_ids = array();
|
||||
|
||||
for ( $i = 0; $i < 3; $i++ ) {
|
||||
$action_ids[] = as_schedule_single_action( $time, $hook, $args, $group );
|
||||
}
|
||||
|
||||
as_unschedule_all_actions( $hook, $args, $group );
|
||||
|
||||
$next = as_next_scheduled_action( $hook );
|
||||
$this->assertFalse( $next );
|
||||
|
||||
$after = as_get_datetime_object( $time );
|
||||
$after->modify( '+1 minute' );
|
||||
|
||||
$store = ActionScheduler::store();
|
||||
|
||||
foreach ( $action_ids as $action_id ) {
|
||||
$action = $store->fetch_action( $action_id );
|
||||
|
||||
$this->assertEquals( $hook, $action->get_hook() );
|
||||
$this->assertEquals( as_get_datetime_object( $time ), $action->get_schedule()->get_date() );
|
||||
$this->assertEquals( ActionScheduler_Store::STATUS_CANCELED, $store->get_status( $action_id ) );
|
||||
$this->assertNull( $action->get_schedule()->get_next( $after ) );
|
||||
}
|
||||
}
|
||||
|
||||
public function test_as_get_datetime_object_default() {
|
||||
|
||||
$utc_now = new ActionScheduler_DateTime( 'now', new DateTimeZone( 'UTC' ) );
|
||||
$as_now = as_get_datetime_object();
|
||||
|
||||
// Don't want to use 'U' as timestamps will always be in UTC.
|
||||
$this->assertEquals( $utc_now->format( 'Y-m-d H:i:s' ), $as_now->format( 'Y-m-d H:i:s' ) );
|
||||
}
|
||||
|
||||
public function test_as_get_datetime_object_relative() {
|
||||
|
||||
$utc_tomorrow = new ActionScheduler_DateTime( 'tomorrow', new DateTimeZone( 'UTC' ) );
|
||||
$as_tomorrow = as_get_datetime_object( 'tomorrow' );
|
||||
|
||||
$this->assertEquals( $utc_tomorrow->format( 'Y-m-d H:i:s' ), $as_tomorrow->format( 'Y-m-d H:i:s' ) );
|
||||
|
||||
$utc_tomorrow = new ActionScheduler_DateTime( 'yesterday', new DateTimeZone( 'UTC' ) );
|
||||
$as_tomorrow = as_get_datetime_object( 'yesterday' );
|
||||
|
||||
$this->assertEquals( $utc_tomorrow->format( 'Y-m-d H:i:s' ), $as_tomorrow->format( 'Y-m-d H:i:s' ) );
|
||||
}
|
||||
|
||||
public function test_as_get_datetime_object_fixed() {
|
||||
|
||||
$utc_tomorrow = new ActionScheduler_DateTime( '29 February 2016', new DateTimeZone( 'UTC' ) );
|
||||
$as_tomorrow = as_get_datetime_object( '29 February 2016' );
|
||||
|
||||
$this->assertEquals( $utc_tomorrow->format( 'Y-m-d H:i:s' ), $as_tomorrow->format( 'Y-m-d H:i:s' ) );
|
||||
|
||||
$utc_tomorrow = new ActionScheduler_DateTime( '1st January 2024', new DateTimeZone( 'UTC' ) );
|
||||
$as_tomorrow = as_get_datetime_object( '1st January 2024' );
|
||||
|
||||
$this->assertEquals( $utc_tomorrow->format( 'Y-m-d H:i:s' ), $as_tomorrow->format( 'Y-m-d H:i:s' ) );
|
||||
}
|
||||
|
||||
public function test_as_get_datetime_object_timezone() {
|
||||
|
||||
$timezone_au = 'Australia/Brisbane';
|
||||
$timezone_default = date_default_timezone_get();
|
||||
|
||||
// phpcs:ignore
|
||||
date_default_timezone_set( $timezone_au );
|
||||
|
||||
$au_now = new ActionScheduler_DateTime( 'now' );
|
||||
$as_now = as_get_datetime_object();
|
||||
|
||||
// Make sure they're for the same time.
|
||||
$this->assertEquals( $au_now->getTimestamp(), $as_now->getTimestamp() );
|
||||
|
||||
// But not in the same timezone, as $as_now should be using UTC.
|
||||
$this->assertNotEquals( $au_now->format( 'Y-m-d H:i:s' ), $as_now->format( 'Y-m-d H:i:s' ) );
|
||||
|
||||
$au_now = new ActionScheduler_DateTime( 'now' );
|
||||
$as_au_now = as_get_datetime_object();
|
||||
|
||||
$this->assertEquals( $au_now->getTimestamp(), $as_now->getTimestamp(), '', 2 );
|
||||
|
||||
// But not in the same timezone, as $as_now should be using UTC.
|
||||
$this->assertNotEquals( $au_now->format( 'Y-m-d H:i:s' ), $as_now->format( 'Y-m-d H:i:s' ) );
|
||||
|
||||
// phpcs:ignore
|
||||
date_default_timezone_set( $timezone_default );
|
||||
}
|
||||
|
||||
public function test_as_get_datetime_object_type() {
|
||||
$f = 'Y-m-d H:i:s';
|
||||
$now = as_get_datetime_object();
|
||||
$this->assertInstanceOf( 'ActionScheduler_DateTime', $now );
|
||||
|
||||
$datetime = new DateTime( 'now', new DateTimeZone( 'UTC' ) );
|
||||
$as_datetime = as_get_datetime_object( $datetime );
|
||||
$this->assertEquals( $datetime->format( $f ), $as_datetime->format( $f ) );
|
||||
}
|
||||
|
||||
public function test_as_has_scheduled_action() {
|
||||
$store = ActionScheduler::store();
|
||||
|
||||
$time = as_get_datetime_object( 'tomorrow' );
|
||||
$action_id = as_schedule_single_action( $time->getTimestamp(), 'hook_1' );
|
||||
|
||||
$this->assertTrue( as_has_scheduled_action( 'hook_1' ) );
|
||||
$this->assertFalse( as_has_scheduled_action( 'hook_2' ) );
|
||||
|
||||
// Go to in-progress.
|
||||
$store->log_execution( $action_id );
|
||||
$this->assertTrue( as_has_scheduled_action( 'hook_1' ) );
|
||||
|
||||
// Go to complete.
|
||||
$store->mark_complete( $action_id );
|
||||
$this->assertFalse( as_has_scheduled_action( 'hook_1' ) );
|
||||
|
||||
// Go to failed.
|
||||
$store->mark_failure( $action_id );
|
||||
$this->assertFalse( as_has_scheduled_action( 'hook_1' ) );
|
||||
}
|
||||
|
||||
public function test_as_has_scheduled_action_with_args() {
|
||||
as_schedule_single_action( time(), 'hook_1', array( 'a' ) );
|
||||
|
||||
$this->assertTrue( as_has_scheduled_action( 'hook_1' ) );
|
||||
$this->assertFalse( as_has_scheduled_action( 'hook_1', array( 'b' ) ) );
|
||||
|
||||
// Test for any args.
|
||||
$this->assertTrue( as_has_scheduled_action( 'hook_1', array( 'a' ) ) );
|
||||
}
|
||||
|
||||
public function test_as_has_scheduled_action_with_group() {
|
||||
as_schedule_single_action( time(), 'hook_1', array(), 'group_1' );
|
||||
|
||||
$this->assertTrue( as_has_scheduled_action( 'hook_1', null, 'group_1' ) );
|
||||
$this->assertTrue( as_has_scheduled_action( 'hook_1', array(), 'group_1' ) );
|
||||
}
|
||||
// phpcs:enable
|
||||
|
||||
/**
|
||||
* Test as_enqueue_async_action with unique param.
|
||||
*/
|
||||
public function test_as_enqueue_async_action_unique() {
|
||||
$this->set_action_scheduler_store( new ActionScheduler_DBStore() );
|
||||
|
||||
$action_id = as_enqueue_async_action( 'hook_1', array( 'a' ), 'dummy', true );
|
||||
$this->assertValidAction( $action_id );
|
||||
|
||||
$action_id_duplicate = as_enqueue_async_action( 'hook_1', array( 'a' ), 'dummy', true );
|
||||
$this->assertEquals( 0, $action_id_duplicate );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test enqueuing a unique action using the hybrid store.
|
||||
* This is using a best-effort approach, so it's possible that the action will be enqueued even if it's not unique.
|
||||
*/
|
||||
public function test_as_enqueue_async_action_unique_hybrid_best_effort() {
|
||||
$this->set_action_scheduler_store( new ActionScheduler_HybridStore() );
|
||||
|
||||
$action_id = as_enqueue_async_action( 'hook_1', array( 'a' ), 'dummy', true );
|
||||
$this->assertValidAction( $action_id );
|
||||
|
||||
$action_id_duplicate = as_enqueue_async_action( 'hook_1', array( 'a' ), 'dummy', true );
|
||||
$this->assertEquals( 0, $action_id_duplicate );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test as_schedule_single_action with unique param.
|
||||
*/
|
||||
public function test_as_schedule_single_action_unique() {
|
||||
$this->set_action_scheduler_store( new ActionScheduler_DBStore() );
|
||||
|
||||
$action_id = as_schedule_single_action( time(), 'hook_1', array( 'a' ), 'dummy', true );
|
||||
$this->assertValidAction( $action_id );
|
||||
|
||||
$action_id_duplicate = as_schedule_single_action( time(), 'hook_1', array( 'a' ), 'dummy', true );
|
||||
$this->assertEquals( 0, $action_id_duplicate );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test as_schedule_recurring_action with unique param.
|
||||
*/
|
||||
public function test_as_schedule_recurring_action_unique() {
|
||||
$this->set_action_scheduler_store( new ActionScheduler_DBStore() );
|
||||
|
||||
$action_id = as_schedule_recurring_action( time(), MINUTE_IN_SECONDS, 'hook_1', array( 'a' ), 'dummy', true );
|
||||
$this->assertValidAction( $action_id );
|
||||
|
||||
$action_id_duplicate = as_schedule_recurring_action( time(), MINUTE_IN_SECONDS, 'hook_1', array( 'a' ), 'dummy', true );
|
||||
$this->assertEquals( 0, $action_id_duplicate );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test as_schedule_cron with unique param.
|
||||
*/
|
||||
public function test_as_schedule_cron_action() {
|
||||
$this->set_action_scheduler_store( new ActionScheduler_DBStore() );
|
||||
|
||||
$action_id = as_schedule_cron_action( time(), '0 0 * * *', 'hook_1', array( 'a' ), 'dummy', true );
|
||||
$this->assertValidAction( $action_id );
|
||||
|
||||
$action_id_duplicate = as_schedule_cron_action( time(), '0 0 * * *', 'hook_1', array( 'a' ), 'dummy', true );
|
||||
$this->assertEquals( 0, $action_id_duplicate );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test recovering from an incorrect database schema when scheduling a single action.
|
||||
*/
|
||||
public function test_as_recover_from_incorrect_schema() {
|
||||
// custom error reporting so we can test for errors sent to error_log.
|
||||
global $wpdb;
|
||||
$wpdb->suppress_errors( true );
|
||||
$error_capture = tmpfile();
|
||||
$actual_error_log = ini_set( 'error_log', stream_get_meta_data( $error_capture )['uri'] ); // phpcs:ignore WordPress.PHP.IniSet.Risky
|
||||
|
||||
// we need a hybrid store so that dropping the priority column will cause an exception.
|
||||
$this->set_action_scheduler_store( new ActionScheduler_HybridStore() );
|
||||
$this->assertEquals( 'ActionScheduler_HybridStore', get_class( ActionScheduler::store() ) );
|
||||
|
||||
// drop the priority column from the actions table.
|
||||
$wpdb->query( "ALTER TABLE {$wpdb->actionscheduler_actions} DROP COLUMN priority" );
|
||||
|
||||
// try to schedule a single action.
|
||||
$action_id = as_schedule_single_action( time(), 'hook_17', array( 'a', 'b' ), 'dummytest', true );
|
||||
|
||||
// ensure that no exception was thrown and zero was returned.
|
||||
$this->assertEquals( 0, $action_id );
|
||||
|
||||
// try to schedule an async action.
|
||||
$action_id = as_enqueue_async_action( 'hook_18', array( 'a', 'b' ), 'dummytest', true );
|
||||
// ensure that no exception was thrown and zero was returned.
|
||||
$this->assertEquals( 0, $action_id );
|
||||
|
||||
// try to schedule a recurring action.
|
||||
$action_id = as_schedule_recurring_action( time(), MINUTE_IN_SECONDS, 'hook_19', array( 'a', 'b' ), 'dummytest', true );
|
||||
// ensure that no exception was thrown and zero was returned.
|
||||
$this->assertEquals( 0, $action_id );
|
||||
|
||||
// try to schedule a cron action.
|
||||
$action_id = as_schedule_cron_action( time(), '0 0 * * *', 'hook_20', array( 'a', 'b' ), 'dummytest', true );
|
||||
// ensure that no exception was thrown and zero was returned.
|
||||
$this->assertEquals( 0, $action_id );
|
||||
|
||||
// ensure that all four errors were logged to error_log.
|
||||
$logged_errors = stream_get_contents( $error_capture );
|
||||
$this->assertContains( 'Caught exception while enqueuing action "hook_17": Error saving action', $logged_errors );
|
||||
$this->assertContains( 'Caught exception while enqueuing action "hook_18": Error saving action', $logged_errors );
|
||||
$this->assertContains( 'Caught exception while enqueuing action "hook_19": Error saving action', $logged_errors );
|
||||
$this->assertContains( 'Caught exception while enqueuing action "hook_20": Error saving action', $logged_errors );
|
||||
$this->assertContains( "Unknown column 'priority' in ", $logged_errors );
|
||||
|
||||
// recreate the priority column.
|
||||
$wpdb->query( "ALTER TABLE {$wpdb->actionscheduler_actions} ADD COLUMN priority tinyint(10) UNSIGNED NOT NULL DEFAULT 10" );
|
||||
// restore error logging.
|
||||
$wpdb->suppress_errors( false );
|
||||
ini_set( 'error_log', $actual_error_log ); // phpcs:ignore WordPress.PHP.IniSet.Risky
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that as_supports returns true for supported features.
|
||||
*/
|
||||
public function test_as_supports_for_supported_feature() {
|
||||
$this->assertTrue( as_supports( 'ensure_recurring_actions_hook' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that as_supports returns false for unsupported features.
|
||||
*/
|
||||
public function test_as_supports_for_unsupported_feature() {
|
||||
$this->assertFalse( as_supports( 'non_existent_feature' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to set actions scheduler store.
|
||||
*
|
||||
* @param ActionScheduler_Store $store Store instance to set.
|
||||
*/
|
||||
private function set_action_scheduler_store( $store ) {
|
||||
$store_factory_setter = function() use ( $store ) {
|
||||
self::$store = $store;
|
||||
};
|
||||
$binded_store_factory_setter = Closure::bind( $store_factory_setter, null, ActionScheduler_Store::class );
|
||||
$binded_store_factory_setter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to assert valid action.
|
||||
*
|
||||
* @param int $action_id Action ID to assert.
|
||||
*/
|
||||
private function assertValidAction( $action_id ) {
|
||||
$this->assertNotEquals( 0, $action_id );
|
||||
$action = ActionScheduler::store()->fetch_action( $action_id );
|
||||
$this->assertInstanceOf( 'ActionScheduler_Action', $action );
|
||||
}
|
||||
}
|
||||
131
tests/phpunit/procedural_api/wc_get_scheduled_actions_Test.php
Normal file
131
tests/phpunit/procedural_api/wc_get_scheduled_actions_Test.php
Normal file
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class as_get_scheduled_actions_Test
|
||||
*/
|
||||
class as_get_scheduled_actions_Test extends ActionScheduler_UnitTestCase {
|
||||
/** @var array<int, string> */
|
||||
private $hooks = array();
|
||||
|
||||
/** @var array<int, string> */
|
||||
private $args = array();
|
||||
|
||||
/** @var array<int, string> */
|
||||
private $groups = array();
|
||||
|
||||
public function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$store = ActionScheduler::store();
|
||||
|
||||
for ( $i = 0; $i < 10; $i++ ) {
|
||||
$this->hooks[ $i ] = md5( wp_rand() );
|
||||
$this->args[ $i ] = md5( wp_rand() );
|
||||
$this->groups[ $i ] = md5( wp_rand() );
|
||||
}
|
||||
|
||||
for ( $i = 0; $i < 10; $i++ ) {
|
||||
for ( $j = 0; $j < 10; $j++ ) {
|
||||
$schedule = new ActionScheduler_SimpleSchedule( as_get_datetime_object( $j - 3 . 'days' ) );
|
||||
$group = $this->groups[ ( $i + $j ) % 10 ];
|
||||
$action = new ActionScheduler_Action( $this->hooks[ $i ], array( $this->args[ $j ] ), $schedule, $group );
|
||||
$store->save_action( $action );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function test_date_queries() {
|
||||
$actions = as_get_scheduled_actions(
|
||||
array(
|
||||
'date' => as_get_datetime_object( gmdate( 'Y-m-d 00:00:00' ) ),
|
||||
'per_page' => -1,
|
||||
),
|
||||
'ids'
|
||||
);
|
||||
$this->assertCount( 30, $actions );
|
||||
|
||||
$actions = as_get_scheduled_actions(
|
||||
array(
|
||||
'date' => as_get_datetime_object( gmdate( 'Y-m-d 00:00:00' ) ),
|
||||
'date_compare' => '>=',
|
||||
'per_page' => -1,
|
||||
),
|
||||
'ids'
|
||||
);
|
||||
$this->assertCount( 70, $actions );
|
||||
}
|
||||
|
||||
public function test_hook_queries() {
|
||||
$actions = as_get_scheduled_actions(
|
||||
array(
|
||||
'hook' => $this->hooks[2],
|
||||
'per_page' => -1,
|
||||
),
|
||||
'ids'
|
||||
);
|
||||
$this->assertCount( 10, $actions );
|
||||
|
||||
$actions = as_get_scheduled_actions(
|
||||
array(
|
||||
'hook' => $this->hooks[2],
|
||||
'date' => as_get_datetime_object( gmdate( 'Y-m-d 00:00:00' ) ),
|
||||
'per_page' => -1,
|
||||
),
|
||||
'ids'
|
||||
);
|
||||
$this->assertCount( 3, $actions );
|
||||
}
|
||||
|
||||
public function test_args_queries() {
|
||||
$actions = as_get_scheduled_actions(
|
||||
array(
|
||||
'args' => array( $this->args[5] ),
|
||||
'per_page' => -1,
|
||||
),
|
||||
'ids'
|
||||
);
|
||||
$this->assertCount( 10, $actions );
|
||||
|
||||
$actions = as_get_scheduled_actions(
|
||||
array(
|
||||
'args' => array( $this->args[5] ),
|
||||
'hook' => $this->hooks[3],
|
||||
'per_page' => -1,
|
||||
),
|
||||
'ids'
|
||||
);
|
||||
$this->assertCount( 1, $actions );
|
||||
|
||||
$actions = as_get_scheduled_actions(
|
||||
array(
|
||||
'args' => array( $this->args[5] ),
|
||||
'hook' => $this->hooks[3],
|
||||
'date' => as_get_datetime_object( gmdate( 'Y-m-d 00:00:00' ) ),
|
||||
'per_page' => -1,
|
||||
),
|
||||
'ids'
|
||||
);
|
||||
$this->assertCount( 0, $actions );
|
||||
}
|
||||
|
||||
public function test_group_queries() {
|
||||
$actions = as_get_scheduled_actions(
|
||||
array(
|
||||
'group' => $this->groups[1],
|
||||
'per_page' => -1,
|
||||
),
|
||||
'ids'
|
||||
);
|
||||
$this->assertCount( 10, $actions );
|
||||
|
||||
$actions = as_get_scheduled_actions(
|
||||
array(
|
||||
'group' => $this->groups[1],
|
||||
'hook' => $this->hooks[9],
|
||||
'per_page' => -1,
|
||||
),
|
||||
'ids'
|
||||
);
|
||||
$this->assertCount( 1, $actions );
|
||||
}
|
||||
}
|
||||
173
tests/phpunit/runner/ActionScheduler_QueueCleaner_Test.php
Normal file
173
tests/phpunit/runner/ActionScheduler_QueueCleaner_Test.php
Normal file
@@ -0,0 +1,173 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class ActionScheduler_QueueCleaner_Test
|
||||
*/
|
||||
class ActionScheduler_QueueCleaner_Test extends ActionScheduler_UnitTestCase {
|
||||
|
||||
public function test_delete_old_actions() {
|
||||
$store = ActionScheduler::store();
|
||||
$runner = ActionScheduler_Mocker::get_queue_runner( $store );
|
||||
$random = md5( wp_rand() );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( as_get_datetime_object( '1 day ago' ) );
|
||||
|
||||
$created_actions = array();
|
||||
for ( $i = 0; $i < 5; $i++ ) {
|
||||
$action = new ActionScheduler_Action( ActionScheduler_Callbacks::HOOK_WITH_CALLBACK, array( $random ), $schedule );
|
||||
$created_actions[] = $store->save_action( $action );
|
||||
}
|
||||
|
||||
$runner->run();
|
||||
|
||||
add_filter( 'action_scheduler_retention_period', '__return_zero' ); // delete any finished job.
|
||||
$cleaner = new ActionScheduler_QueueCleaner( $store );
|
||||
$cleaned = $cleaner->delete_old_actions();
|
||||
remove_filter( 'action_scheduler_retention_period', '__return_zero' );
|
||||
|
||||
$this->assertIsArray( $cleaned, 'ActionScheduler_QueueCleaner::delete_old_actions() returns an array.' );
|
||||
$this->assertCount( 5, $cleaned, 'ActionScheduler_QueueCleaner::delete_old_actions() deleted the expected number of actions.' );
|
||||
|
||||
foreach ( $created_actions as $action_id ) {
|
||||
$action = $store->fetch_action( $action_id );
|
||||
$this->assertFalse( $action->is_finished() ); // it's a NullAction.
|
||||
}
|
||||
}
|
||||
|
||||
public function test_invalid_retention_period_filter_hook() {
|
||||
// Supplying a non-integer such as null would break under 3.5.4 and earlier.
|
||||
add_filter( 'action_scheduler_retention_period', '__return_null' );
|
||||
$cleaner = new ActionScheduler_QueueCleaner( ActionScheduler::store() );
|
||||
|
||||
$this->setExpectedIncorrectUsage( 'ActionScheduler_QueueCleaner::delete_old_actions' );
|
||||
$result = $cleaner->delete_old_actions();
|
||||
remove_filter( 'action_scheduler_retention_period', '__return_zero' );
|
||||
|
||||
$this->assertIsArray(
|
||||
$result,
|
||||
'ActionScheduler_QueueCleaner::delete_old_actions() can be invoked without a fatal error, even if the retention period was invalid.'
|
||||
);
|
||||
|
||||
$this->assertCount(
|
||||
0,
|
||||
$result,
|
||||
'ActionScheduler_QueueCleaner::delete_old_actions() will not delete any actions if the retention period was invalid.'
|
||||
);
|
||||
}
|
||||
|
||||
public function test_delete_canceled_actions() {
|
||||
$store = ActionScheduler::store();
|
||||
|
||||
$random = md5( wp_rand() );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( as_get_datetime_object( '1 day ago' ) );
|
||||
|
||||
$created_actions = array();
|
||||
for ( $i = 0; $i < 5; $i++ ) {
|
||||
$action = new ActionScheduler_Action( $random, array( $random ), $schedule );
|
||||
$action_id = $store->save_action( $action );
|
||||
$store->cancel_action( $action_id );
|
||||
$created_actions[] = $action_id;
|
||||
}
|
||||
|
||||
// track the actions that are deleted.
|
||||
$mock_action = new MockAction();
|
||||
add_action( 'action_scheduler_deleted_action', array( $mock_action, 'action' ), 10, 1 );
|
||||
add_filter( 'action_scheduler_retention_period', '__return_zero' ); // delete any finished job.
|
||||
|
||||
$cleaner = new ActionScheduler_QueueCleaner( $store );
|
||||
$cleaner->delete_old_actions();
|
||||
|
||||
remove_filter( 'action_scheduler_retention_period', '__return_zero' );
|
||||
remove_action( 'action_scheduler_deleted_action', array( $mock_action, 'action' ), 10 );
|
||||
|
||||
$deleted_actions = array();
|
||||
foreach ( $mock_action->get_args() as $action ) {
|
||||
$deleted_actions[] = reset( $action );
|
||||
}
|
||||
|
||||
$this->assertEqualSets( $created_actions, $deleted_actions );
|
||||
}
|
||||
|
||||
public function test_do_not_delete_recent_actions() {
|
||||
$store = ActionScheduler::store();
|
||||
$runner = ActionScheduler_Mocker::get_queue_runner( $store );
|
||||
$random = md5( wp_rand() );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( as_get_datetime_object( '1 day ago' ) );
|
||||
|
||||
$created_actions = array();
|
||||
for ( $i = 0; $i < 5; $i++ ) {
|
||||
$action = new ActionScheduler_Action( $random, array( $random ), $schedule );
|
||||
$created_actions[] = $store->save_action( $action );
|
||||
}
|
||||
|
||||
$runner->run();
|
||||
|
||||
$cleaner = new ActionScheduler_QueueCleaner( $store );
|
||||
$cleaner->delete_old_actions();
|
||||
|
||||
foreach ( $created_actions as $action_id ) {
|
||||
$action = $store->fetch_action( $action_id );
|
||||
$this->assertTrue( $action->is_finished() ); // It's a FinishedAction.
|
||||
}
|
||||
}
|
||||
|
||||
public function test_reset_unrun_actions() {
|
||||
$store = ActionScheduler::store();
|
||||
|
||||
$random = md5( wp_rand() );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( as_get_datetime_object( '1 day ago' ) );
|
||||
|
||||
$created_actions = array();
|
||||
for ( $i = 0; $i < 5; $i++ ) {
|
||||
$action = new ActionScheduler_Action( $random, array( $random ), $schedule );
|
||||
$created_actions[] = $store->save_action( $action );
|
||||
}
|
||||
|
||||
$store->stake_claim( 10 );
|
||||
|
||||
// don't actually process the jobs, to simulate a request that timed out.
|
||||
|
||||
add_filter( 'action_scheduler_timeout_period', '__return_zero' ); // delete any finished job.
|
||||
$cleaner = new ActionScheduler_QueueCleaner( $store );
|
||||
$cleaner->reset_timeouts();
|
||||
|
||||
remove_filter( 'action_scheduler_timeout_period', '__return_zero' );
|
||||
|
||||
$claim = $store->stake_claim( 10 );
|
||||
$this->assertEqualSets( $created_actions, $claim->get_actions() );
|
||||
}
|
||||
|
||||
public function test_do_not_reset_failed_action() {
|
||||
$store = ActionScheduler::store();
|
||||
$random = md5( wp_rand() );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( as_get_datetime_object( '1 day ago' ) );
|
||||
|
||||
$created_actions = array();
|
||||
for ( $i = 0; $i < 5; $i++ ) {
|
||||
$action = new ActionScheduler_Action( $random, array( $random ), $schedule );
|
||||
$created_actions[] = $store->save_action( $action );
|
||||
}
|
||||
|
||||
$claim = $store->stake_claim( 10 );
|
||||
foreach ( $claim->get_actions() as $action_id ) {
|
||||
// simulate the first action interrupted by an uncatchable fatal error.
|
||||
$store->log_execution( $action_id );
|
||||
break;
|
||||
}
|
||||
|
||||
add_filter( 'action_scheduler_timeout_period', '__return_zero' ); // delete any finished job.
|
||||
$cleaner = new ActionScheduler_QueueCleaner( $store );
|
||||
$cleaner->reset_timeouts();
|
||||
remove_filter( 'action_scheduler_timeout_period', '__return_zero' );
|
||||
|
||||
$new_claim = $store->stake_claim( 10 );
|
||||
$this->assertCount( 4, $new_claim->get_actions() );
|
||||
|
||||
add_filter( 'action_scheduler_failure_period', '__return_zero' );
|
||||
$cleaner->mark_failures();
|
||||
remove_filter( 'action_scheduler_failure_period', '__return_zero' );
|
||||
|
||||
$failed = $store->query_actions( array( 'status' => ActionScheduler_Store::STATUS_FAILED ) );
|
||||
$this->assertEquals( $created_actions[0], $failed[0] );
|
||||
$this->assertCount( 1, $failed );
|
||||
}
|
||||
}
|
||||
613
tests/phpunit/runner/ActionScheduler_QueueRunner_Test.php
Normal file
613
tests/phpunit/runner/ActionScheduler_QueueRunner_Test.php
Normal file
@@ -0,0 +1,613 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class ActionScheduler_QueueRunner_Test
|
||||
* @group runners
|
||||
*/
|
||||
class ActionScheduler_QueueRunner_Test extends ActionScheduler_UnitTestCase {
|
||||
public function test_create_runner() {
|
||||
$store = ActionScheduler::store();
|
||||
$runner = ActionScheduler_Mocker::get_queue_runner( $store );
|
||||
$actions_run = $runner->run();
|
||||
|
||||
$this->assertEquals( 0, $actions_run );
|
||||
}
|
||||
|
||||
public function test_run() {
|
||||
$store = ActionScheduler::store();
|
||||
$runner = ActionScheduler_Mocker::get_queue_runner( $store );
|
||||
$mock = new MockAction();
|
||||
$random = md5( wp_rand() );
|
||||
|
||||
add_action( $random, array( $mock, 'action' ) );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( as_get_datetime_object( '1 day ago' ) );
|
||||
|
||||
for ( $i = 0; $i < 5; $i++ ) {
|
||||
$action = new ActionScheduler_Action( $random, array( $random ), $schedule );
|
||||
$store->save_action( $action );
|
||||
}
|
||||
|
||||
$actions_run = $runner->run();
|
||||
|
||||
remove_action( $random, array( $mock, 'action' ) );
|
||||
|
||||
$this->assertEquals( 5, $mock->get_call_count() );
|
||||
$this->assertEquals( 5, $actions_run );
|
||||
}
|
||||
|
||||
public function test_run_with_future_actions() {
|
||||
$store = ActionScheduler::store();
|
||||
$runner = ActionScheduler_Mocker::get_queue_runner( $store );
|
||||
$mock = new MockAction();
|
||||
$random = md5( wp_rand() );
|
||||
|
||||
add_action( $random, array( $mock, 'action' ) );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( as_get_datetime_object( '1 day ago' ) );
|
||||
|
||||
for ( $i = 0; $i < 3; $i++ ) {
|
||||
$action = new ActionScheduler_Action( $random, array( $random ), $schedule );
|
||||
$store->save_action( $action );
|
||||
}
|
||||
|
||||
$schedule = new ActionScheduler_SimpleSchedule( as_get_datetime_object( 'tomorrow' ) );
|
||||
for ( $i = 0; $i < 3; $i++ ) {
|
||||
$action = new ActionScheduler_Action( $random, array( $random ), $schedule );
|
||||
$store->save_action( $action );
|
||||
}
|
||||
|
||||
$actions_run = $runner->run();
|
||||
|
||||
remove_action( $random, array( $mock, 'action' ) );
|
||||
|
||||
$this->assertEquals( 3, $mock->get_call_count() );
|
||||
$this->assertEquals( 3, $actions_run );
|
||||
}
|
||||
|
||||
/**
|
||||
* When an action is processed, it is set to "in-progress" (running) status immediately before the
|
||||
* callback is invoked. If this fails (which could be because it is already in progress) then the
|
||||
* action should be skipped.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_run_with_action_that_is_already_in_progress() {
|
||||
$store = ActionScheduler::store();
|
||||
$hook = uniqid();
|
||||
$callback = function () {};
|
||||
$count = 0;
|
||||
$actions = array();
|
||||
$completed = array();
|
||||
$schedule = new ActionScheduler_SimpleSchedule( as_get_datetime_object( '1 day ago' ) );
|
||||
|
||||
for ( $i = 0; $i < 3; $i++ ) {
|
||||
$actions[] = $store->save_action( new ActionScheduler_Action( $hook, array( $hook ), $schedule ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* This function "sabotages" the next action by prematurely setting its status to "in-progress", simulating
|
||||
* an edge case where a concurrent process runs the action.
|
||||
*/
|
||||
$saboteur = function () use ( &$count, $store, $actions ) {
|
||||
if ( 0 === $count++ ) {
|
||||
$store->log_execution( $actions[1] );
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param int $action_id The ID of the recently completed action.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
$spy = function ( $action_id ) use ( &$completed ) {
|
||||
$completed[] = $action_id;
|
||||
};
|
||||
|
||||
add_action( 'action_scheduler_begin_execute', $saboteur );
|
||||
add_action( 'action_scheduler_completed_action', $spy );
|
||||
add_action( $hook, $callback );
|
||||
|
||||
$actions_attempted = ActionScheduler_Mocker::get_queue_runner( $store )->run();
|
||||
|
||||
remove_action( 'action_scheduler_begin_execute', $saboteur );
|
||||
remove_action( 'action_scheduler_completed_action', $spy );
|
||||
remove_action( $hook, $callback );
|
||||
|
||||
$this->assertEquals( 3, $actions_attempted, 'The queue runner attempted to process all 3 actions.' );
|
||||
$this->assertEquals( array( $actions[0], $actions[2] ), $completed, 'Only two of the three actions were completed (one was skipped, because it was processed by a concurrent request).' );
|
||||
}
|
||||
|
||||
public function test_completed_action_status() {
|
||||
$store = ActionScheduler::store();
|
||||
$runner = ActionScheduler_Mocker::get_queue_runner( $store );
|
||||
$random = md5( wp_rand() );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( as_get_datetime_object( '12 hours ago' ) );
|
||||
$action = new ActionScheduler_Action( $random, array(), $schedule );
|
||||
$action_id = $store->save_action( $action );
|
||||
|
||||
$runner->run();
|
||||
|
||||
$finished_action = $store->fetch_action( $action_id );
|
||||
|
||||
$this->assertTrue( $finished_action->is_finished() );
|
||||
}
|
||||
|
||||
public function test_next_instance_of_cron_action() {
|
||||
// Create an action with daily Cron expression (i.e. midnight each day).
|
||||
$random = md5( wp_rand() );
|
||||
$action_id = ActionScheduler::factory()->cron( $random, array(), null, '0 0 * * *' );
|
||||
$store = ActionScheduler::store();
|
||||
$runner = ActionScheduler_Mocker::get_queue_runner( $store );
|
||||
|
||||
// Make sure the 1st instance of the action is scheduled to occur tomorrow.
|
||||
$date = as_get_datetime_object( 'tomorrow' );
|
||||
$date->modify( '-1 minute' );
|
||||
$claim = $store->stake_claim( 10, $date );
|
||||
$this->assertCount( 0, $claim->get_actions() );
|
||||
|
||||
$store->release_claim( $claim );
|
||||
|
||||
$date->modify( '+1 minute' );
|
||||
|
||||
$claim = $store->stake_claim( 10, $date );
|
||||
$actions = $claim->get_actions();
|
||||
$this->assertCount( 1, $actions );
|
||||
|
||||
$fetched_action_id = reset( $actions );
|
||||
$fetched_action = $store->fetch_action( $fetched_action_id );
|
||||
|
||||
$this->assertEquals( $fetched_action_id, $action_id );
|
||||
$this->assertEquals( $random, $fetched_action->get_hook() );
|
||||
$this->assertEquals( $date->getTimestamp(), $fetched_action->get_schedule()->get_date()->getTimestamp(), '', 1 );
|
||||
|
||||
$store->release_claim( $claim );
|
||||
|
||||
// Make sure the 2nd instance of the cron action is scheduled to occur tomorrow still.
|
||||
$runner->process_action( $action_id );
|
||||
|
||||
$claim = $store->stake_claim( 10, $date );
|
||||
$actions = $claim->get_actions();
|
||||
$this->assertCount( 1, $actions );
|
||||
|
||||
$fetched_action_id = reset( $actions );
|
||||
$fetched_action = $store->fetch_action( $fetched_action_id );
|
||||
|
||||
$this->assertNotEquals( $fetched_action_id, $action_id );
|
||||
$this->assertEquals( $random, $fetched_action->get_hook() );
|
||||
$this->assertEquals( $date->getTimestamp(), $fetched_action->get_schedule()->get_date()->getTimestamp(), '', 1 );
|
||||
}
|
||||
|
||||
public function test_next_instance_of_interval_action() {
|
||||
$random = md5( wp_rand() );
|
||||
$date = as_get_datetime_object( '12 hours ago' );
|
||||
$store = ActionScheduler::store();
|
||||
$runner = ActionScheduler_Mocker::get_queue_runner( $store );
|
||||
|
||||
// Create an action to recur every 24 hours, with the first instance scheduled to run 12 hours ago.
|
||||
$action_id = ActionScheduler::factory()->create(
|
||||
array(
|
||||
'type' => 'recurring',
|
||||
'hook' => $random,
|
||||
'when' => $date->getTimestamp(),
|
||||
'pattern' => DAY_IN_SECONDS,
|
||||
'priority' => 2,
|
||||
)
|
||||
);
|
||||
|
||||
// Make sure the 1st instance of the action is scheduled to occur 12 hours ago.
|
||||
$claim = $store->stake_claim( 10, $date );
|
||||
$actions = $claim->get_actions();
|
||||
$this->assertCount( 1, $actions );
|
||||
|
||||
$fetched_action_id = reset( $actions );
|
||||
$fetched_action = $store->fetch_action( $fetched_action_id );
|
||||
|
||||
$this->assertEquals( $fetched_action_id, $action_id );
|
||||
$this->assertEquals( $random, $fetched_action->get_hook() );
|
||||
$this->assertEquals( $date->getTimestamp(), $fetched_action->get_schedule()->get_date()->getTimestamp(), '', 1 );
|
||||
|
||||
$store->release_claim( $claim );
|
||||
|
||||
// Make sure after the queue is run, the 2nd instance of the action is scheduled to occur in 24 hours.
|
||||
$runner->run();
|
||||
|
||||
$date = as_get_datetime_object( '+1 day' );
|
||||
$claim = $store->stake_claim( 10, $date );
|
||||
$actions = $claim->get_actions();
|
||||
$this->assertCount( 1, $actions );
|
||||
|
||||
$fetched_action_id = reset( $actions );
|
||||
$fetched_action = $store->fetch_action( $fetched_action_id );
|
||||
|
||||
$this->assertNotEquals( $fetched_action_id, $action_id );
|
||||
$this->assertEquals( $random, $fetched_action->get_hook() );
|
||||
$this->assertEquals( $date->getTimestamp(), $fetched_action->get_schedule()->get_date()->getTimestamp(), '', 1 );
|
||||
$this->assertEquals( 2, $fetched_action->get_priority(), 'The replacement action should inherit the same priority as the original action.' );
|
||||
$store->release_claim( $claim );
|
||||
|
||||
// Make sure the 3rd instance of the cron action is scheduled for 24 hours from now, as the action was run early, ahead of schedule.
|
||||
$runner->process_action( $fetched_action_id );
|
||||
$date = as_get_datetime_object( '+1 day' );
|
||||
|
||||
$claim = $store->stake_claim( 10, $date );
|
||||
$actions = $claim->get_actions();
|
||||
$this->assertCount( 1, $actions );
|
||||
|
||||
$fetched_action_id = reset( $actions );
|
||||
$fetched_action = $store->fetch_action( $fetched_action_id );
|
||||
|
||||
$this->assertNotEquals( $fetched_action_id, $action_id );
|
||||
$this->assertEquals( $random, $fetched_action->get_hook() );
|
||||
$this->assertEquals( $date->getTimestamp(), $fetched_action->get_schedule()->get_date()->getTimestamp(), '', 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* As soon as one recurring action has been executed its replacement will be scheduled.
|
||||
*
|
||||
* This is true even if the current action fails. This makes sense, since a failure may be temporary in nature.
|
||||
* However, if the same recurring action consistently fails then it is likely that there is a problem and we should
|
||||
* stop creating new instances. This test outlines the expected behavior in this regard.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_failing_recurring_actions_are_not_rescheduled_when_threshold_met() {
|
||||
$store = ActionScheduler_Store::instance();
|
||||
$runner = ActionScheduler_Mocker::get_queue_runner( $store );
|
||||
$created_actions = array();
|
||||
|
||||
// Create 4 failed actions (one below the threshold of what counts as 'consistently failing').
|
||||
for ( $i = 0; $i < 3; $i++ ) {
|
||||
// We give each action a unique set of args, this illustrates that in the context of determining consistent
|
||||
// failure we care only about the hook and not other properties of the action.
|
||||
$args = array( 'unique-' . $i => hash( 'md5', $i ) );
|
||||
$hook = 'will-fail';
|
||||
$date = as_get_datetime_object( 12 - $i . ' hours ago' );
|
||||
$action_id = as_schedule_recurring_action( $date->getTimestamp(), HOUR_IN_SECONDS, $hook, $args );
|
||||
$store->mark_failure( $action_id );
|
||||
$created_actions[] = $action_id;
|
||||
}
|
||||
|
||||
// Now create a further action using the same hook, that is also destined to fail.
|
||||
$date = as_get_datetime_object( '6 hours ago' );
|
||||
$pending_action_id = as_schedule_recurring_action( $date->getTimestamp(), HOUR_IN_SECONDS, $hook, $args );
|
||||
$created_actions[] = $pending_action_id;
|
||||
|
||||
// Process the queue!
|
||||
$runner->run();
|
||||
|
||||
$pending_actions = $store->query_actions(
|
||||
array(
|
||||
'hook' => $hook,
|
||||
'args' => $args,
|
||||
'status' => ActionScheduler_Store::STATUS_PENDING,
|
||||
)
|
||||
);
|
||||
|
||||
$new_pending_action_id = current( $pending_actions );
|
||||
|
||||
// We now have 5 instances of the same recurring action. 4 have already failed, one is pending.
|
||||
$this->assertCount( 1, $pending_actions, 'If the threshold for consistent failure has not been met, a replacement action should have been scheduled.' );
|
||||
$this->assertNotContains( $new_pending_action_id, $created_actions, 'Confirm that the replacement action is new, and not one of those we created manually earlier in the test.' );
|
||||
|
||||
// Process the pending action (we do this directly instead of via `$runner->run()` because it won't actually
|
||||
// become due for another hour).
|
||||
$runner->process_action( $new_pending_action_id );
|
||||
$pending_actions = $store->query_actions(
|
||||
array(
|
||||
'hook' => $hook,
|
||||
'args' => $args,
|
||||
'status' => ActionScheduler_Store::STATUS_PENDING,
|
||||
)
|
||||
);
|
||||
|
||||
// Now 5 instances of the same recurring action have all failed, therefore the threshold for consistent failure
|
||||
// has been met and, this time, a new action should *not* have been scheduled.
|
||||
$this->assertCount( 0, $pending_actions, 'The failure threshold (5 consecutive fails for recurring actions with the same signature) having been met, no further actions were scheduled.' );
|
||||
}
|
||||
|
||||
/**
|
||||
* If a recurring action continually fails, it will not be re-scheduled. However, a hook makes it possible to
|
||||
* exempt specific actions from this behavior (without impacting other unrelated recurring actions).
|
||||
*
|
||||
* @see self::test_failing_recurring_actions_are_not_rescheduled_when_threshold_met()
|
||||
* @return void
|
||||
*/
|
||||
public function test_exceptions_can_be_made_for_failing_recurring_actions() {
|
||||
$store = ActionScheduler_Store::instance();
|
||||
$runner = ActionScheduler_Mocker::get_queue_runner( $store );
|
||||
$observed = 0;
|
||||
|
||||
// Create 2 sets of 5 actions that have already past and have already failed (five being the threshold of what
|
||||
// counts as 'consistently failing').
|
||||
for ( $i = 0; $i < 4; $i++ ) {
|
||||
$date = as_get_datetime_object( 12 - $i . ' hours ago' );
|
||||
$store->mark_failure( as_schedule_recurring_action( $date->getTimestamp(), HOUR_IN_SECONDS, 'foo' ) );
|
||||
$store->mark_failure( as_schedule_recurring_action( $date->getTimestamp(), HOUR_IN_SECONDS, 'bar' ) );
|
||||
}
|
||||
|
||||
// Add one more action (pending and past-due) to each set.
|
||||
$date = as_get_datetime_object( '6 hours ago' );
|
||||
as_schedule_recurring_action( $date->getTimestamp(), HOUR_IN_SECONDS, 'foo' );
|
||||
as_schedule_recurring_action( $date->getTimestamp(), HOUR_IN_SECONDS, 'bar' );
|
||||
|
||||
// Define a filter function that allows scheduled actions for hook 'foo' to still be rescheduled, despite its
|
||||
// history of consistent failure.
|
||||
$filter = function( $is_failing, $action ) use ( &$observed ) {
|
||||
$observed++;
|
||||
return 'foo' === $action->get_hook() ? false : $is_failing;
|
||||
};
|
||||
|
||||
// Process the queue with our consistent-failure filter function in place.
|
||||
add_filter( 'action_scheduler_recurring_action_is_consistently_failing', $filter, 10, 2 );
|
||||
$runner->run();
|
||||
|
||||
// Check how many (if any) of our test actions were re-scheduled.
|
||||
$pending_foo_actions = $store->query_actions(
|
||||
array(
|
||||
'hook' => 'foo',
|
||||
'status' => ActionScheduler_Store::STATUS_PENDING,
|
||||
)
|
||||
);
|
||||
$pending_bar_actions = $store->query_actions(
|
||||
array(
|
||||
'hook' => 'bar',
|
||||
'status' => ActionScheduler_Store::STATUS_PENDING,
|
||||
)
|
||||
);
|
||||
|
||||
// Expectations...
|
||||
$this->assertCount( 1, $pending_foo_actions, 'We expect a new instance of action "foo" will have been scheduled.' );
|
||||
$this->assertCount( 0, $pending_bar_actions, 'We expect no further instances of action "bar" will have been scheduled.' );
|
||||
$this->assertEquals( 2, $observed, 'We expect our callback to have been invoked twice, once in relation to each test action.' );
|
||||
|
||||
// Clean-up...
|
||||
remove_filter( 'action_scheduler_recurring_action_is_consistently_failing', $filter, 10, 2 );
|
||||
}
|
||||
|
||||
public function test_hooked_into_wp_cron() {
|
||||
$next = wp_next_scheduled( ActionScheduler_QueueRunner::WP_CRON_HOOK, array( 'WP Cron' ) );
|
||||
$this->assertNotEmpty( $next );
|
||||
}
|
||||
|
||||
public function test_batch_count_limit() {
|
||||
$store = ActionScheduler::store();
|
||||
$runner = ActionScheduler_Mocker::get_queue_runner( $store );
|
||||
$mock = new MockAction();
|
||||
$random = md5( wp_rand() );
|
||||
|
||||
add_action( $random, array( $mock, 'action' ) );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( new ActionScheduler_DateTime( '1 day ago' ) );
|
||||
|
||||
for ( $i = 0; $i < 2; $i++ ) {
|
||||
$action = new ActionScheduler_Action( $random, array( $random ), $schedule );
|
||||
$store->save_action( $action );
|
||||
}
|
||||
|
||||
$claim = $store->stake_claim();
|
||||
|
||||
$actions_run = $runner->run();
|
||||
|
||||
$this->assertEquals( 0, $mock->get_call_count() );
|
||||
$this->assertEquals( 0, $actions_run );
|
||||
|
||||
$store->release_claim( $claim );
|
||||
|
||||
$actions_run = $runner->run();
|
||||
|
||||
$this->assertEquals( 2, $mock->get_call_count() );
|
||||
$this->assertEquals( 2, $actions_run );
|
||||
|
||||
remove_action( $random, array( $mock, 'action' ) );
|
||||
}
|
||||
|
||||
public function test_changing_batch_count_limit() {
|
||||
$store = ActionScheduler::store();
|
||||
$runner = ActionScheduler_Mocker::get_queue_runner( $store );
|
||||
$random = md5( wp_rand() );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( new ActionScheduler_DateTime( '1 day ago' ) );
|
||||
|
||||
for ( $i = 0; $i < 30; $i++ ) {
|
||||
$action = new ActionScheduler_Action( $random, array( $random ), $schedule );
|
||||
$store->save_action( $action );
|
||||
}
|
||||
|
||||
$claims = array();
|
||||
|
||||
for ( $i = 0; $i < 5; $i++ ) {
|
||||
$claims[] = $store->stake_claim( 5 );
|
||||
}
|
||||
|
||||
$mock1 = new MockAction();
|
||||
add_action( $random, array( $mock1, 'action' ) );
|
||||
$actions_run = $runner->run();
|
||||
remove_action( $random, array( $mock1, 'action' ) );
|
||||
|
||||
$this->assertEquals( 0, $mock1->get_call_count() );
|
||||
$this->assertEquals( 0, $actions_run );
|
||||
|
||||
add_filter( 'action_scheduler_queue_runner_concurrent_batches', array( $this, 'return_6' ) );
|
||||
|
||||
$mock2 = new MockAction();
|
||||
add_action( $random, array( $mock2, 'action' ) );
|
||||
$actions_run = $runner->run();
|
||||
remove_action( $random, array( $mock2, 'action' ) );
|
||||
|
||||
$this->assertEquals( 5, $mock2->get_call_count() );
|
||||
$this->assertEquals( 5, $actions_run );
|
||||
|
||||
remove_filter( 'action_scheduler_queue_runner_concurrent_batches', array( $this, 'return_6' ) );
|
||||
|
||||
for ( $i = 0; $i < 5; $i++ ) { // to make up for the actions we just processed.
|
||||
$action = new ActionScheduler_Action( $random, array( $random ), $schedule );
|
||||
$store->save_action( $action );
|
||||
}
|
||||
|
||||
$mock3 = new MockAction();
|
||||
add_action( $random, array( $mock3, 'action' ) );
|
||||
$actions_run = $runner->run();
|
||||
remove_action( $random, array( $mock3, 'action' ) );
|
||||
|
||||
$this->assertEquals( 0, $mock3->get_call_count() );
|
||||
$this->assertEquals( 0, $actions_run );
|
||||
|
||||
remove_filter( 'action_scheduler_queue_runner_concurrent_batches', array( $this, 'return_6' ) );
|
||||
}
|
||||
|
||||
public function return_6() {
|
||||
return 6;
|
||||
}
|
||||
|
||||
public function test_store_fetch_action_failure_schedule_next_instance() {
|
||||
$random = md5( wp_rand() );
|
||||
$schedule = new ActionScheduler_IntervalSchedule( as_get_datetime_object( '12 hours ago' ), DAY_IN_SECONDS );
|
||||
$action = new ActionScheduler_Action( $random, array(), $schedule );
|
||||
$action_id = ActionScheduler::store()->save_action( $action );
|
||||
|
||||
// Set up a mock store that will throw an exception when fetching actions.
|
||||
$store = $this
|
||||
->getMockBuilder( 'ActionScheduler_wpPostStore' )
|
||||
->setMethods( array( 'fetch_action' ) )
|
||||
->getMock();
|
||||
$store
|
||||
->method( 'fetch_action' )
|
||||
->with( array( $action_id ) )
|
||||
->will( $this->throwException( new Exception() ) );
|
||||
|
||||
// Set up a mock queue runner to verify that schedule_next_instance()
|
||||
// isn't called for an undefined $action.
|
||||
$runner = $this
|
||||
->getMockBuilder( 'ActionScheduler_QueueRunner' )
|
||||
->setConstructorArgs( array( $store ) )
|
||||
->setMethods( array( 'schedule_next_instance' ) )
|
||||
->getMock();
|
||||
$runner
|
||||
->expects( $this->never() )
|
||||
->method( 'schedule_next_instance' );
|
||||
|
||||
$runner->run();
|
||||
|
||||
// Set up a mock store that will throw an exception when fetching actions.
|
||||
$store2 = $this
|
||||
->getMockBuilder( 'ActionScheduler_wpPostStore' )
|
||||
->setMethods( array( 'fetch_action' ) )
|
||||
->getMock();
|
||||
$store2
|
||||
->method( 'fetch_action' )
|
||||
->with( array( $action_id ) )
|
||||
->willReturn( null );
|
||||
|
||||
// Set up a mock queue runner to verify that schedule_next_instance()
|
||||
// isn't called for an undefined $action.
|
||||
$runner2 = $this
|
||||
->getMockBuilder( 'ActionScheduler_QueueRunner' )
|
||||
->setConstructorArgs( array( $store ) )
|
||||
->setMethods( array( 'schedule_next_instance' ) )
|
||||
->getMock();
|
||||
$runner2
|
||||
->expects( $this->never() )
|
||||
->method( 'schedule_next_instance' );
|
||||
|
||||
$runner2->run();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that actions are processed in the correct order. Specifically, that past-due actions are not
|
||||
* penalized in favor of newer async actions.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_order_in_which_actions_are_processed() {
|
||||
/** @var ActionScheduler_Store $store */
|
||||
$store = ActionScheduler::store();
|
||||
$runner = ActionScheduler_Mocker::get_queue_runner( $store );
|
||||
$execution_order = array();
|
||||
$past_due_action = as_schedule_single_action( time() - HOUR_IN_SECONDS, __METHOD__, array( 'execute' => 'first' ) );
|
||||
$async_action = as_enqueue_async_action( __METHOD__, array( 'execute' => 'second' ) );
|
||||
|
||||
$monitor = function ( $order ) use ( &$execution_order ) {
|
||||
$execution_order[] = $order;
|
||||
};
|
||||
|
||||
add_action( __METHOD__, $monitor );
|
||||
$runner->run();
|
||||
remove_action( __METHOD__, $monitor );
|
||||
|
||||
$this->assertEquals(
|
||||
array(
|
||||
'first',
|
||||
'second',
|
||||
),
|
||||
$execution_order
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the ability of the queue runner to accommodate a range of error conditions (raised recoverable errors
|
||||
* under PHP 5.6, thrown errors under PHP 7.0 upwards, and exceptions under all supported versions).
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_recoverable_errors_do_not_break_queue_runner() {
|
||||
$executed = 0;
|
||||
as_enqueue_async_action( 'foo' );
|
||||
as_enqueue_async_action( 'bar' );
|
||||
as_enqueue_async_action( 'baz' );
|
||||
as_enqueue_async_action( 'foobar' );
|
||||
|
||||
/**
|
||||
* Trigger a custom user error.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
$foo = function () use ( &$executed ) {
|
||||
$executed++;
|
||||
trigger_error( 'Trouble.', E_USER_ERROR ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error
|
||||
};
|
||||
|
||||
/**
|
||||
* Throw an exception.
|
||||
*
|
||||
* @throws Exception Intentionally raised for testing purposes.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
$bar = function () use ( &$executed ) {
|
||||
$executed++;
|
||||
throw new Exception( 'More trouble.' );
|
||||
};
|
||||
|
||||
/**
|
||||
* Trigger a recoverable fatal error. Under PHP 5.6 the error will be raised, and under PHP 7.0 and higher the
|
||||
* error will be thrown (different mechanisms are needed to support this difference).
|
||||
*
|
||||
* @throws Throwable Intentionally raised for testing purposes.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
$baz = function () use ( &$executed ) {
|
||||
$executed++;
|
||||
(string) (object) array();
|
||||
};
|
||||
|
||||
/**
|
||||
* A problem-free callback.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
$foobar = function () use ( &$executed ) {
|
||||
$executed++;
|
||||
};
|
||||
|
||||
add_action( 'foo', $foo );
|
||||
add_action( 'bar', $bar );
|
||||
add_action( 'baz', $baz );
|
||||
add_action( 'foobar', $foobar );
|
||||
|
||||
ActionScheduler_Mocker::get_queue_runner( ActionScheduler::store() )->run();
|
||||
$this->assertEquals( 4, $executed, 'All enqueued actions ran as expected despite errors and exceptions being raised by the first actions in the set.' );
|
||||
|
||||
remove_action( 'foo', $foo );
|
||||
remove_action( 'bar', $bar );
|
||||
remove_action( 'baz', $baz );
|
||||
remove_action( 'foobar', $foobar );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class ActionScheduler_CronSchedule_Test
|
||||
* @group schedules
|
||||
*/
|
||||
class ActionScheduler_CronSchedule_Test extends ActionScheduler_UnitTestCase {
|
||||
public function test_creation() {
|
||||
$time = as_get_datetime_object( 'tomorrow' );
|
||||
$cron = CronExpression::factory( '@daily' );
|
||||
$start = clone $time;
|
||||
$start->modify( '-1 hour' );
|
||||
$schedule = new ActionScheduler_CronSchedule( $start, $cron );
|
||||
$this->assertEquals( $time, $schedule->get_date() );
|
||||
$this->assertEquals( $start, $schedule->get_first_date() );
|
||||
|
||||
// Test delaying for a future start date.
|
||||
$start->modify( '+1 week' );
|
||||
$time->modify( '+1 week' );
|
||||
|
||||
$schedule = new ActionScheduler_CronSchedule( $start, $cron );
|
||||
$this->assertEquals( $time, $schedule->get_date() );
|
||||
$this->assertEquals( $start, $schedule->get_first_date() );
|
||||
}
|
||||
|
||||
public function test_creation_with_first_date() {
|
||||
$time = as_get_datetime_object( 'tomorrow' );
|
||||
$cron = CronExpression::factory( '@daily' );
|
||||
$start = clone $time;
|
||||
$start->modify( '-1 hour' );
|
||||
$schedule = new ActionScheduler_CronSchedule( $start, $cron );
|
||||
$this->assertEquals( $time, $schedule->get_date() );
|
||||
$this->assertEquals( $start, $schedule->get_first_date() );
|
||||
|
||||
// Test delaying for a future start date.
|
||||
$first = clone $time;
|
||||
$first->modify( '-1 day' );
|
||||
$start->modify( '+1 week' );
|
||||
$time->modify( '+1 week' );
|
||||
|
||||
$schedule = new ActionScheduler_CronSchedule( $start, $cron, $first );
|
||||
$this->assertEquals( $time, $schedule->get_date() );
|
||||
$this->assertEquals( $first, $schedule->get_first_date() );
|
||||
}
|
||||
|
||||
public function test_next() {
|
||||
$time = as_get_datetime_object( '2013-06-14' );
|
||||
$cron = CronExpression::factory( '@daily' );
|
||||
$schedule = new ActionScheduler_CronSchedule( $time, $cron );
|
||||
$this->assertEquals( as_get_datetime_object( 'tomorrow' ), $schedule->get_next( as_get_datetime_object() ) );
|
||||
}
|
||||
|
||||
public function test_is_recurring() {
|
||||
$schedule = new ActionScheduler_CronSchedule( as_get_datetime_object( '2013-06-14' ), CronExpression::factory( '@daily' ) );
|
||||
$this->assertTrue( $schedule->is_recurring() );
|
||||
}
|
||||
|
||||
public function test_cron_format() {
|
||||
$time = as_get_datetime_object( '2014-01-01' );
|
||||
$cron = CronExpression::factory( '0 0 10 10 *' );
|
||||
$schedule = new ActionScheduler_CronSchedule( $time, $cron );
|
||||
$this->assertEquals( as_get_datetime_object( '2014-10-10' ), $schedule->get_date() );
|
||||
|
||||
$cron = CronExpression::factory( '0 0 L 1/2 *' );
|
||||
$schedule = new ActionScheduler_CronSchedule( $time, $cron );
|
||||
$this->assertEquals( as_get_datetime_object( '2014-01-31' ), $schedule->get_date() );
|
||||
$this->assertEquals( as_get_datetime_object( '2014-07-31' ), $schedule->get_next( as_get_datetime_object( '2014-06-01' ) ) );
|
||||
$this->assertEquals( as_get_datetime_object( '2028-11-30' ), $schedule->get_next( as_get_datetime_object( '2028-11-01' ) ) );
|
||||
|
||||
$cron = CronExpression::factory( '30 14 * * MON#3 *' );
|
||||
$schedule = new ActionScheduler_CronSchedule( $time, $cron );
|
||||
$this->assertEquals( as_get_datetime_object( '2014-01-20 14:30:00' ), $schedule->get_date() );
|
||||
$this->assertEquals( as_get_datetime_object( '2014-05-19 14:30:00' ), $schedule->get_next( as_get_datetime_object( '2014-05-01' ) ) );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class ActionScheduler_IntervalSchedule_Test
|
||||
* @group schedules
|
||||
*/
|
||||
class ActionScheduler_IntervalSchedule_Test extends ActionScheduler_UnitTestCase {
|
||||
public function test_creation() {
|
||||
$time = as_get_datetime_object();
|
||||
$schedule = new ActionScheduler_IntervalSchedule( $time, HOUR_IN_SECONDS );
|
||||
$this->assertEquals( $time, $schedule->get_date() );
|
||||
$this->assertEquals( $time, $schedule->get_first_date() );
|
||||
}
|
||||
|
||||
public function test_creation_with_first_date() {
|
||||
$first = as_get_datetime_object();
|
||||
$time = as_get_datetime_object( '+12 hours' );
|
||||
$schedule = new ActionScheduler_IntervalSchedule( $time, HOUR_IN_SECONDS, $first );
|
||||
$this->assertEquals( $time, $schedule->get_date() );
|
||||
$this->assertEquals( $first, $schedule->get_first_date() );
|
||||
}
|
||||
|
||||
public function test_next() {
|
||||
$now = time();
|
||||
$start = $now - 30;
|
||||
$schedule = new ActionScheduler_IntervalSchedule( as_get_datetime_object( "@$start" ), MINUTE_IN_SECONDS );
|
||||
$this->assertEquals( $start, $schedule->get_date()->getTimestamp() );
|
||||
$this->assertEquals( $now + MINUTE_IN_SECONDS, $schedule->get_next( as_get_datetime_object() )->getTimestamp() );
|
||||
$this->assertEquals( $start, $schedule->get_next( as_get_datetime_object( "@$start" ) )->getTimestamp() );
|
||||
}
|
||||
|
||||
public function test_is_recurring() {
|
||||
$start = time() - 30;
|
||||
$schedule = new ActionScheduler_IntervalSchedule( as_get_datetime_object( "@$start" ), MINUTE_IN_SECONDS );
|
||||
$this->assertTrue( $schedule->is_recurring() );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class ActionScheduler_NullSchedule_Test
|
||||
* @group schedules
|
||||
*/
|
||||
class ActionScheduler_NullSchedule_Test extends ActionScheduler_UnitTestCase {
|
||||
public function test_null_schedule() {
|
||||
$schedule = new ActionScheduler_NullSchedule();
|
||||
$this->assertNull( $schedule->get_date() );
|
||||
}
|
||||
|
||||
public function test_is_recurring() {
|
||||
$schedule = new ActionScheduler_NullSchedule();
|
||||
$this->assertFalse( $schedule->is_recurring() );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class ActionScheduler_SimpleSchedule_Test
|
||||
* @group schedules
|
||||
*/
|
||||
class ActionScheduler_SimpleSchedule_Test extends ActionScheduler_UnitTestCase {
|
||||
public function test_creation() {
|
||||
$time = as_get_datetime_object();
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$this->assertEquals( $time, $schedule->get_date() );
|
||||
}
|
||||
|
||||
public function test_past_date() {
|
||||
$time = as_get_datetime_object( '-1 day' );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$this->assertEquals( $time, $schedule->get_date() );
|
||||
}
|
||||
|
||||
public function test_future_date() {
|
||||
$time = as_get_datetime_object( '+1 day' );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$this->assertEquals( $time, $schedule->get_date() );
|
||||
}
|
||||
|
||||
public function test_grace_period_for_next() {
|
||||
$time = as_get_datetime_object( '3 seconds ago' );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $time );
|
||||
$this->assertEquals( $time, $schedule->get_date() );
|
||||
}
|
||||
|
||||
public function test_is_recurring() {
|
||||
$schedule = new ActionScheduler_SimpleSchedule( as_get_datetime_object( '+1 day' ) );
|
||||
$this->assertFalse( $schedule->is_recurring() );
|
||||
}
|
||||
}
|
||||
42
tests/phpunit/versioning/ActionScheduler_Versions_Test.php
Normal file
42
tests/phpunit/versioning/ActionScheduler_Versions_Test.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class ActionScheduler_Versions_Test
|
||||
*/
|
||||
class ActionScheduler_Versions_Test extends ActionScheduler_UnitTestCase {
|
||||
public function test_register_version() {
|
||||
$versions = new ActionScheduler_Versions();
|
||||
$versions->register( '1.0-dev', 'callback_1_dot_0_dev' );
|
||||
$versions->register( '1.0', 'callback_1_dot_0' );
|
||||
|
||||
$registered = $versions->get_versions();
|
||||
|
||||
$this->assertArrayHasKey( '1.0-dev', $registered );
|
||||
$this->assertArrayHasKey( '1.0', $registered );
|
||||
$this->assertCount( 2, $registered );
|
||||
|
||||
$this->assertEquals( 'callback_1_dot_0_dev', $registered['1.0-dev'] );
|
||||
}
|
||||
|
||||
public function test_duplicate_version() {
|
||||
$versions = new ActionScheduler_Versions();
|
||||
$versions->register( '1.0', 'callback_1_dot_0_a' );
|
||||
$versions->register( '1.0', 'callback_1_dot_0_b' );
|
||||
|
||||
$registered = $versions->get_versions();
|
||||
|
||||
$this->assertArrayHasKey( '1.0', $registered );
|
||||
$this->assertCount( 1, $registered );
|
||||
}
|
||||
|
||||
public function test_latest_version() {
|
||||
$versions = new ActionScheduler_Versions();
|
||||
$this->assertEquals( '__return_null', $versions->latest_version_callback() );
|
||||
$versions->register( '1.2', 'callback_1_dot_2' );
|
||||
$versions->register( '1.3', 'callback_1_dot_3' );
|
||||
$versions->register( '1.0', 'callback_1_dot_0' );
|
||||
|
||||
$this->assertEquals( '1.3', $versions->latest_version() );
|
||||
$this->assertEquals( 'callback_1_dot_3', $versions->latest_version_callback() );
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user