abstract-testcase.php 37 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318
  1. <?php
  2. require_once __DIR__ . '/factory.php';
  3. require_once __DIR__ . '/trac.php';
  4. /**
  5. * Defines a basic fixture to run multiple tests.
  6. *
  7. * Resets the state of the WordPress installation before and after every test.
  8. *
  9. * Includes utility functions and assertions useful for testing WordPress.
  10. *
  11. * All WordPress unit tests should inherit from this class.
  12. */
  13. abstract class WP_UnitTestCase_Base extends PHPUnit\Framework\TestCase {
  14. protected static $forced_tickets = array();
  15. protected $expected_deprecated = array();
  16. protected $caught_deprecated = array();
  17. protected $expected_doing_it_wrong = array();
  18. protected $caught_doing_it_wrong = array();
  19. protected static $hooks_saved = array();
  20. protected static $ignore_files;
  21. public function __isset( $name ) {
  22. return 'factory' === $name;
  23. }
  24. public function __get( $name ) {
  25. if ( 'factory' === $name ) {
  26. return self::factory();
  27. }
  28. }
  29. /**
  30. * Fetches the factory object for generating WordPress fixtures.
  31. *
  32. * @return WP_UnitTest_Factory The fixture factory.
  33. */
  34. protected static function factory() {
  35. static $factory = null;
  36. if ( ! $factory ) {
  37. $factory = new WP_UnitTest_Factory();
  38. }
  39. return $factory;
  40. }
  41. /**
  42. * Retrieves the name of the class the static method is called in.
  43. *
  44. * @deprecated 5.3.0 Use the PHP native get_called_class() function instead.
  45. *
  46. * @return string The class name.
  47. */
  48. public static function get_called_class() {
  49. return get_called_class();
  50. }
  51. /**
  52. * Runs the routine before setting up all tests.
  53. */
  54. public static function setUpBeforeClass() {
  55. global $wpdb;
  56. $wpdb->suppress_errors = false;
  57. $wpdb->show_errors = true;
  58. $wpdb->db_connect();
  59. ini_set( 'display_errors', 1 );
  60. parent::setUpBeforeClass();
  61. $class = get_called_class();
  62. if ( method_exists( $class, 'wpSetUpBeforeClass' ) ) {
  63. call_user_func( array( $class, 'wpSetUpBeforeClass' ), self::factory() );
  64. }
  65. self::commit_transaction();
  66. }
  67. /**
  68. * Runs the routine after all tests have been run.
  69. */
  70. public static function tearDownAfterClass() {
  71. parent::tearDownAfterClass();
  72. _delete_all_data();
  73. self::flush_cache();
  74. $class = get_called_class();
  75. if ( method_exists( $class, 'wpTearDownAfterClass' ) ) {
  76. call_user_func( array( $class, 'wpTearDownAfterClass' ) );
  77. }
  78. self::commit_transaction();
  79. }
  80. /**
  81. * Runs the routine before each test is executed.
  82. */
  83. public function setUp() {
  84. set_time_limit( 0 );
  85. if ( ! self::$ignore_files ) {
  86. self::$ignore_files = $this->scan_user_uploads();
  87. }
  88. if ( ! self::$hooks_saved ) {
  89. $this->_backup_hooks();
  90. }
  91. global $wp_rewrite;
  92. $this->clean_up_global_scope();
  93. /*
  94. * When running core tests, ensure that post types and taxonomies
  95. * are reset for each test. We skip this step for non-core tests,
  96. * given the large number of plugins that register post types and
  97. * taxonomies at 'init'.
  98. */
  99. if ( defined( 'WP_RUN_CORE_TESTS' ) && WP_RUN_CORE_TESTS ) {
  100. $this->reset_post_types();
  101. $this->reset_taxonomies();
  102. $this->reset_post_statuses();
  103. $this->reset__SERVER();
  104. if ( $wp_rewrite->permalink_structure ) {
  105. $this->set_permalink_structure( '' );
  106. }
  107. }
  108. $this->start_transaction();
  109. $this->expectDeprecated();
  110. add_filter( 'wp_die_handler', array( $this, 'get_wp_die_handler' ) );
  111. }
  112. /**
  113. * After a test method runs, reset any state in WordPress the test method might have changed.
  114. */
  115. public function tearDown() {
  116. global $wpdb, $wp_query, $wp;
  117. $wpdb->query( 'ROLLBACK' );
  118. if ( is_multisite() ) {
  119. while ( ms_is_switched() ) {
  120. restore_current_blog();
  121. }
  122. }
  123. $wp_query = new WP_Query();
  124. $wp = new WP();
  125. // Reset globals related to the post loop and `setup_postdata()`.
  126. $post_globals = array( 'post', 'id', 'authordata', 'currentday', 'currentmonth', 'page', 'pages', 'multipage', 'more', 'numpages' );
  127. foreach ( $post_globals as $global ) {
  128. $GLOBALS[ $global ] = null;
  129. }
  130. // Reset $wp_sitemap global so that sitemap-related dynamic $wp->public_query_vars are added when the next test runs.
  131. $GLOBALS['wp_sitemaps'] = null;
  132. $this->unregister_all_meta_keys();
  133. remove_theme_support( 'html5' );
  134. remove_filter( 'query', array( $this, '_create_temporary_tables' ) );
  135. remove_filter( 'query', array( $this, '_drop_temporary_tables' ) );
  136. remove_filter( 'wp_die_handler', array( $this, 'get_wp_die_handler' ) );
  137. $this->_restore_hooks();
  138. wp_set_current_user( 0 );
  139. }
  140. /**
  141. * Cleans the global scope (e.g `$_GET` and `$_POST`).
  142. */
  143. public function clean_up_global_scope() {
  144. $_GET = array();
  145. $_POST = array();
  146. self::flush_cache();
  147. }
  148. /**
  149. * Allow tests to be skipped on some automated runs.
  150. *
  151. * For test runs on Travis/GitHub Actions for something other than trunk/master,
  152. * we want to skip tests that only need to run for master.
  153. */
  154. public function skipOnAutomatedBranches() {
  155. // https://docs.travis-ci.com/user/environment-variables/#Default-Environment-Variables
  156. $travis_branch = getenv( 'TRAVIS_BRANCH' );
  157. $travis_pull_request = getenv( 'TRAVIS_PULL_REQUEST' );
  158. // https://docs.github.com/en/free-pro-team@latest/actions/reference/environment-variables#default-environment-variables
  159. $github_event_name = getenv( 'GITHUB_EVENT_NAME' );
  160. $github_ref = getenv( 'GITHUB_REF' );
  161. if ( $github_event_name && 'false' !== $github_event_name ) {
  162. // We're on GitHub Actions.
  163. $skipped = array( 'pull_request', 'pull_request_target' );
  164. if ( in_array( $github_event_name, $skipped, true ) || 'refs/heads/master' !== $github_ref ) {
  165. $this->markTestSkipped( 'For automated test runs, this test is only run on trunk/master' );
  166. }
  167. } elseif ( $travis_branch && 'false' !== $travis_branch ) {
  168. // We're on Travis CI.
  169. if ( 'master' !== $travis_branch || 'false' !== $travis_pull_request ) {
  170. $this->markTestSkipped( 'For automated test runs, this test is only run on trunk/master' );
  171. }
  172. }
  173. }
  174. /**
  175. * Allow tests to be skipped when Multisite is not in use.
  176. *
  177. * Use in conjunction with the ms-required group.
  178. */
  179. public function skipWithoutMultisite() {
  180. if ( ! is_multisite() ) {
  181. $this->markTestSkipped( 'Test only runs on Multisite' );
  182. }
  183. }
  184. /**
  185. * Allow tests to be skipped when Multisite is in use.
  186. *
  187. * Use in conjunction with the ms-excluded group.
  188. */
  189. public function skipWithMultisite() {
  190. if ( is_multisite() ) {
  191. $this->markTestSkipped( 'Test does not run on Multisite' );
  192. }
  193. }
  194. /**
  195. * Allow tests to be skipped if the HTTP request times out.
  196. *
  197. * @param array|WP_Error $response HTTP response.
  198. */
  199. public function skipTestOnTimeout( $response ) {
  200. if ( ! is_wp_error( $response ) ) {
  201. return;
  202. }
  203. if ( 'connect() timed out!' === $response->get_error_message() ) {
  204. $this->markTestSkipped( 'HTTP timeout' );
  205. }
  206. if ( false !== strpos( $response->get_error_message(), 'timed out after' ) ) {
  207. $this->markTestSkipped( 'HTTP timeout' );
  208. }
  209. if ( 0 === strpos( $response->get_error_message(), 'stream_socket_client(): unable to connect to tcp://s.w.org:80' ) ) {
  210. $this->markTestSkipped( 'HTTP timeout' );
  211. }
  212. }
  213. /**
  214. * Unregister existing post types and register defaults.
  215. *
  216. * Run before each test in order to clean up the global scope, in case
  217. * a test forgets to unregister a post type on its own, or fails before
  218. * it has a chance to do so.
  219. */
  220. protected function reset_post_types() {
  221. foreach ( get_post_types( array(), 'objects' ) as $pt ) {
  222. if ( empty( $pt->tests_no_auto_unregister ) ) {
  223. _unregister_post_type( $pt->name );
  224. }
  225. }
  226. create_initial_post_types();
  227. }
  228. /**
  229. * Unregister existing taxonomies and register defaults.
  230. *
  231. * Run before each test in order to clean up the global scope, in case
  232. * a test forgets to unregister a taxonomy on its own, or fails before
  233. * it has a chance to do so.
  234. */
  235. protected function reset_taxonomies() {
  236. foreach ( get_taxonomies() as $tax ) {
  237. _unregister_taxonomy( $tax );
  238. }
  239. create_initial_taxonomies();
  240. }
  241. /**
  242. * Unregister non-built-in post statuses.
  243. */
  244. protected function reset_post_statuses() {
  245. foreach ( get_post_stati( array( '_builtin' => false ) ) as $post_status ) {
  246. _unregister_post_status( $post_status );
  247. }
  248. }
  249. /**
  250. * Reset `$_SERVER` variables
  251. */
  252. protected function reset__SERVER() {
  253. tests_reset__SERVER();
  254. }
  255. /**
  256. * Saves the action and filter-related globals so they can be restored later.
  257. *
  258. * Stores $wp_actions, $wp_current_filter, and $wp_filter on a class variable
  259. * so they can be restored on tearDown() using _restore_hooks().
  260. *
  261. * @global array $wp_actions
  262. * @global array $wp_current_filter
  263. * @global array $wp_filter
  264. */
  265. protected function _backup_hooks() {
  266. $globals = array( 'wp_actions', 'wp_current_filter' );
  267. foreach ( $globals as $key ) {
  268. self::$hooks_saved[ $key ] = $GLOBALS[ $key ];
  269. }
  270. self::$hooks_saved['wp_filter'] = array();
  271. foreach ( $GLOBALS['wp_filter'] as $hook_name => $hook_object ) {
  272. self::$hooks_saved['wp_filter'][ $hook_name ] = clone $hook_object;
  273. }
  274. }
  275. /**
  276. * Restores the hook-related globals to their state at setUp()
  277. * so that future tests aren't affected by hooks set during this last test.
  278. *
  279. * @global array $wp_actions
  280. * @global array $wp_current_filter
  281. * @global array $wp_filter
  282. */
  283. protected function _restore_hooks() {
  284. $globals = array( 'wp_actions', 'wp_current_filter' );
  285. foreach ( $globals as $key ) {
  286. if ( isset( self::$hooks_saved[ $key ] ) ) {
  287. $GLOBALS[ $key ] = self::$hooks_saved[ $key ];
  288. }
  289. }
  290. if ( isset( self::$hooks_saved['wp_filter'] ) ) {
  291. $GLOBALS['wp_filter'] = array();
  292. foreach ( self::$hooks_saved['wp_filter'] as $hook_name => $hook_object ) {
  293. $GLOBALS['wp_filter'][ $hook_name ] = clone $hook_object;
  294. }
  295. }
  296. }
  297. /**
  298. * Flushes the WordPress object cache.
  299. */
  300. public static function flush_cache() {
  301. global $wp_object_cache;
  302. $wp_object_cache->group_ops = array();
  303. $wp_object_cache->stats = array();
  304. $wp_object_cache->memcache_debug = array();
  305. $wp_object_cache->cache = array();
  306. if ( method_exists( $wp_object_cache, '__remoteset' ) ) {
  307. $wp_object_cache->__remoteset();
  308. }
  309. wp_cache_flush();
  310. wp_cache_add_global_groups( array( 'users', 'userlogins', 'usermeta', 'user_meta', 'useremail', 'userslugs', 'site-transient', 'site-options', 'blog-lookup', 'blog-details', 'rss', 'global-posts', 'blog-id-cache', 'networks', 'sites', 'site-details', 'blog_meta' ) );
  311. wp_cache_add_non_persistent_groups( array( 'comment', 'counts', 'plugins' ) );
  312. }
  313. /**
  314. * Clean up any registered meta keys.
  315. *
  316. * @since 5.1.0
  317. *
  318. * @global array $wp_meta_keys
  319. */
  320. public function unregister_all_meta_keys() {
  321. global $wp_meta_keys;
  322. if ( ! is_array( $wp_meta_keys ) ) {
  323. return;
  324. }
  325. foreach ( $wp_meta_keys as $object_type => $type_keys ) {
  326. foreach ( $type_keys as $object_subtype => $subtype_keys ) {
  327. foreach ( $subtype_keys as $key => $value ) {
  328. unregister_meta_key( $object_type, $key, $object_subtype );
  329. }
  330. }
  331. }
  332. }
  333. /**
  334. * Starts a database transaction.
  335. */
  336. public function start_transaction() {
  337. global $wpdb;
  338. $wpdb->query( 'SET autocommit = 0;' );
  339. $wpdb->query( 'START TRANSACTION;' );
  340. add_filter( 'query', array( $this, '_create_temporary_tables' ) );
  341. add_filter( 'query', array( $this, '_drop_temporary_tables' ) );
  342. }
  343. /**
  344. * Commit the queries in a transaction.
  345. *
  346. * @since 4.1.0
  347. */
  348. public static function commit_transaction() {
  349. global $wpdb;
  350. $wpdb->query( 'COMMIT;' );
  351. }
  352. /**
  353. * Replaces the `CREATE TABLE` statement with a `CREATE TEMPORARY TABLE` statement.
  354. *
  355. * @param string $query The query to replace the statement for.
  356. * @return string The altered query.
  357. */
  358. public function _create_temporary_tables( $query ) {
  359. if ( 0 === strpos( trim( $query ), 'CREATE TABLE' ) ) {
  360. return substr_replace( trim( $query ), 'CREATE TEMPORARY TABLE', 0, 12 );
  361. }
  362. return $query;
  363. }
  364. /**
  365. * Replaces the `DROP TABLE` statement with a `DROP TEMPORARY TABLE` statement.
  366. *
  367. * @param string $query The query to replace the statement for.
  368. * @return string The altered query.
  369. */
  370. public function _drop_temporary_tables( $query ) {
  371. if ( 0 === strpos( trim( $query ), 'DROP TABLE' ) ) {
  372. return substr_replace( trim( $query ), 'DROP TEMPORARY TABLE', 0, 10 );
  373. }
  374. return $query;
  375. }
  376. /**
  377. * Retrieves the `wp_die()` handler.
  378. *
  379. * @param callable $handler The current die handler.
  380. * @return callable The test die handler.
  381. */
  382. public function get_wp_die_handler( $handler ) {
  383. return array( $this, 'wp_die_handler' );
  384. }
  385. /**
  386. * Throws an exception when called.
  387. *
  388. * @throws WPDieException Exception containing the message.
  389. *
  390. * @param string $message The `wp_die()` message.
  391. */
  392. public function wp_die_handler( $message ) {
  393. if ( is_wp_error( $message ) ) {
  394. $message = $message->get_error_message();
  395. }
  396. if ( ! is_scalar( $message ) ) {
  397. $message = '0';
  398. }
  399. throw new WPDieException( $message );
  400. }
  401. /**
  402. * Sets up the expectations for testing a deprecated call.
  403. */
  404. public function expectDeprecated() {
  405. $annotations = $this->getAnnotations();
  406. foreach ( array( 'class', 'method' ) as $depth ) {
  407. if ( ! empty( $annotations[ $depth ]['expectedDeprecated'] ) ) {
  408. $this->expected_deprecated = array_merge( $this->expected_deprecated, $annotations[ $depth ]['expectedDeprecated'] );
  409. }
  410. if ( ! empty( $annotations[ $depth ]['expectedIncorrectUsage'] ) ) {
  411. $this->expected_doing_it_wrong = array_merge( $this->expected_doing_it_wrong, $annotations[ $depth ]['expectedIncorrectUsage'] );
  412. }
  413. }
  414. add_action( 'deprecated_function_run', array( $this, 'deprecated_function_run' ) );
  415. add_action( 'deprecated_argument_run', array( $this, 'deprecated_function_run' ) );
  416. add_action( 'deprecated_hook_run', array( $this, 'deprecated_function_run' ) );
  417. add_action( 'doing_it_wrong_run', array( $this, 'doing_it_wrong_run' ) );
  418. add_action( 'deprecated_function_trigger_error', '__return_false' );
  419. add_action( 'deprecated_argument_trigger_error', '__return_false' );
  420. add_action( 'deprecated_hook_trigger_error', '__return_false' );
  421. add_action( 'doing_it_wrong_trigger_error', '__return_false' );
  422. }
  423. /**
  424. * Handles a deprecated expectation.
  425. *
  426. * The DocBlock should contain `@expectedDeprecated` to trigger this.
  427. */
  428. public function expectedDeprecated() {
  429. $errors = array();
  430. $not_caught_deprecated = array_diff( $this->expected_deprecated, $this->caught_deprecated );
  431. foreach ( $not_caught_deprecated as $not_caught ) {
  432. $errors[] = "Failed to assert that $not_caught triggered a deprecated notice";
  433. }
  434. $unexpected_deprecated = array_diff( $this->caught_deprecated, $this->expected_deprecated );
  435. foreach ( $unexpected_deprecated as $unexpected ) {
  436. $errors[] = "Unexpected deprecated notice for $unexpected";
  437. }
  438. $not_caught_doing_it_wrong = array_diff( $this->expected_doing_it_wrong, $this->caught_doing_it_wrong );
  439. foreach ( $not_caught_doing_it_wrong as $not_caught ) {
  440. $errors[] = "Failed to assert that $not_caught triggered an incorrect usage notice";
  441. }
  442. $unexpected_doing_it_wrong = array_diff( $this->caught_doing_it_wrong, $this->expected_doing_it_wrong );
  443. foreach ( $unexpected_doing_it_wrong as $unexpected ) {
  444. $errors[] = "Unexpected incorrect usage notice for $unexpected";
  445. }
  446. // Perform an assertion, but only if there are expected or unexpected deprecated calls or wrongdoings.
  447. if ( ! empty( $this->expected_deprecated ) ||
  448. ! empty( $this->expected_doing_it_wrong ) ||
  449. ! empty( $this->caught_deprecated ) ||
  450. ! empty( $this->caught_doing_it_wrong ) ) {
  451. $this->assertEmpty( $errors, implode( "\n", $errors ) );
  452. }
  453. }
  454. /**
  455. * Detect post-test failure conditions.
  456. *
  457. * We use this method to detect expectedDeprecated and expectedIncorrectUsage annotations.
  458. *
  459. * @since 4.2.0
  460. */
  461. protected function assertPostConditions() {
  462. $this->expectedDeprecated();
  463. }
  464. /**
  465. * Declare an expected `_deprecated_function()` or `_deprecated_argument()` call from within a test.
  466. *
  467. * @since 4.2.0
  468. *
  469. * @param string $deprecated Name of the function, method, class, or argument that is deprecated. Must match
  470. * the first parameter of the `_deprecated_function()` or `_deprecated_argument()` call.
  471. */
  472. public function setExpectedDeprecated( $deprecated ) {
  473. $this->expected_deprecated[] = $deprecated;
  474. }
  475. /**
  476. * Declare an expected `_doing_it_wrong()` call from within a test.
  477. *
  478. * @since 4.2.0
  479. *
  480. * @param string $doing_it_wrong Name of the function, method, or class that appears in the first argument
  481. * of the source `_doing_it_wrong()` call.
  482. */
  483. public function setExpectedIncorrectUsage( $doing_it_wrong ) {
  484. $this->expected_doing_it_wrong[] = $doing_it_wrong;
  485. }
  486. /**
  487. * PHPUnit 6+ compatibility shim.
  488. *
  489. * @param mixed $exception
  490. * @param string $message
  491. * @param int|string $code
  492. */
  493. public function setExpectedException( $exception, $message = '', $code = null ) {
  494. if ( method_exists( 'PHPUnit_Framework_TestCase', 'setExpectedException' ) ) {
  495. parent::setExpectedException( $exception, $message, $code );
  496. } else {
  497. $this->expectException( $exception );
  498. if ( '' !== $message ) {
  499. $this->expectExceptionMessage( $message );
  500. }
  501. if ( null !== $code ) {
  502. $this->expectExceptionCode( $code );
  503. }
  504. }
  505. }
  506. /**
  507. * Adds a deprecated function to the list of caught deprecated calls.
  508. *
  509. * @param string $function The deprecated function.
  510. */
  511. public function deprecated_function_run( $function ) {
  512. if ( ! in_array( $function, $this->caught_deprecated, true ) ) {
  513. $this->caught_deprecated[] = $function;
  514. }
  515. }
  516. /**
  517. * Adds a function called in a wrong way to the list of `_doing_it_wrong()` calls.
  518. *
  519. * @param string $function The function to add.
  520. */
  521. public function doing_it_wrong_run( $function ) {
  522. if ( ! in_array( $function, $this->caught_doing_it_wrong, true ) ) {
  523. $this->caught_doing_it_wrong[] = $function;
  524. }
  525. }
  526. /**
  527. * Asserts that the given value is an instance of WP_Error.
  528. *
  529. * @param mixed $actual The value to check.
  530. * @param string $message Optional. Message to display when the assertion fails.
  531. */
  532. public function assertWPError( $actual, $message = '' ) {
  533. $this->assertInstanceOf( 'WP_Error', $actual, $message );
  534. }
  535. /**
  536. * Asserts that the given value is not an instance of WP_Error.
  537. *
  538. * @param mixed $actual The value to check.
  539. * @param string $message Optional. Message to display when the assertion fails.
  540. */
  541. public function assertNotWPError( $actual, $message = '' ) {
  542. if ( '' === $message && is_wp_error( $actual ) ) {
  543. $message = $actual->get_error_message();
  544. }
  545. $this->assertNotInstanceOf( 'WP_Error', $actual, $message );
  546. }
  547. /**
  548. * Asserts that the given value is an instance of IXR_Error.
  549. *
  550. * @param mixed $actual The value to check.
  551. * @param string $message Optional. Message to display when the assertion fails.
  552. */
  553. public function assertIXRError( $actual, $message = '' ) {
  554. $this->assertInstanceOf( 'IXR_Error', $actual, $message );
  555. }
  556. /**
  557. * Asserts that the given value is not an instance of IXR_Error.
  558. *
  559. * @param mixed $actual The value to check.
  560. * @param string $message Optional. Message to display when the assertion fails.
  561. */
  562. public function assertNotIXRError( $actual, $message = '' ) {
  563. if ( $actual instanceof IXR_Error && '' === $message ) {
  564. $message = $actual->message;
  565. }
  566. $this->assertNotInstanceOf( 'IXR_Error', $actual, $message );
  567. }
  568. /**
  569. * Asserts that the given fields are present in the given object.
  570. *
  571. * @param object $object The object to check.
  572. * @param array $fields The fields to check.
  573. */
  574. public function assertEqualFields( $object, $fields ) {
  575. foreach ( $fields as $field_name => $field_value ) {
  576. if ( $object->$field_name !== $field_value ) {
  577. $this->fail();
  578. }
  579. }
  580. }
  581. /**
  582. * Asserts that two values are equal, with whitespace differences discarded.
  583. *
  584. * @param string $expected The expected value.
  585. * @param string $actual The actual value.
  586. */
  587. public function assertDiscardWhitespace( $expected, $actual ) {
  588. $this->assertEquals( preg_replace( '/\s*/', '', $expected ), preg_replace( '/\s*/', '', $actual ) );
  589. }
  590. /**
  591. * Asserts that two values have the same type and value, with EOL differences discarded.
  592. *
  593. * @since 5.6.0
  594. *
  595. * @param string $expected The expected value.
  596. * @param string $actual The actual value.
  597. */
  598. public function assertSameIgnoreEOL( $expected, $actual ) {
  599. $this->assertSame( str_replace( "\r\n", "\n", $expected ), str_replace( "\r\n", "\n", $actual ) );
  600. }
  601. /**
  602. * Asserts that two values are equal, with EOL differences discarded.
  603. *
  604. * @since 5.4.0
  605. * @since 5.6.0 Turned into an alias for `::assertSameIgnoreEOL()`.
  606. *
  607. * @param string $expected The expected value.
  608. * @param string $actual The actual value.
  609. */
  610. public function assertEqualsIgnoreEOL( $expected, $actual ) {
  611. $this->assertSameIgnoreEOL( $expected, $actual );
  612. }
  613. /**
  614. * Asserts that the contents of two un-keyed, single arrays are the same, without accounting for the order of elements.
  615. *
  616. * @since 5.6.0
  617. *
  618. * @param array $expected Expected array.
  619. * @param array $actual Array to check.
  620. */
  621. public function assertSameSets( $expected, $actual ) {
  622. sort( $expected );
  623. sort( $actual );
  624. $this->assertSame( $expected, $actual );
  625. }
  626. /**
  627. * Asserts that the contents of two un-keyed, single arrays are equal, without accounting for the order of elements.
  628. *
  629. * @since 3.5.0
  630. *
  631. * @param array $expected Expected array.
  632. * @param array $actual Array to check.
  633. */
  634. public function assertEqualSets( $expected, $actual ) {
  635. sort( $expected );
  636. sort( $actual );
  637. $this->assertEquals( $expected, $actual );
  638. }
  639. /**
  640. * Asserts that the contents of two keyed, single arrays are the same, without accounting for the order of elements.
  641. *
  642. * @since 5.6.0
  643. *
  644. * @param array $expected Expected array.
  645. * @param array $actual Array to check.
  646. */
  647. public function assertSameSetsWithIndex( $expected, $actual ) {
  648. ksort( $expected );
  649. ksort( $actual );
  650. $this->assertSame( $expected, $actual );
  651. }
  652. /**
  653. * Asserts that the contents of two keyed, single arrays are equal, without accounting for the order of elements.
  654. *
  655. * @since 4.1.0
  656. *
  657. * @param array $expected Expected array.
  658. * @param array $actual Array to check.
  659. */
  660. public function assertEqualSetsWithIndex( $expected, $actual ) {
  661. ksort( $expected );
  662. ksort( $actual );
  663. $this->assertEquals( $expected, $actual );
  664. }
  665. /**
  666. * Asserts that the given variable is a multidimensional array, and that all arrays are non-empty.
  667. *
  668. * @since 4.8.0
  669. *
  670. * @param array $array Array to check.
  671. */
  672. public function assertNonEmptyMultidimensionalArray( $array ) {
  673. $this->assertTrue( is_array( $array ) );
  674. $this->assertNotEmpty( $array );
  675. foreach ( $array as $sub_array ) {
  676. $this->assertTrue( is_array( $sub_array ) );
  677. $this->assertNotEmpty( $sub_array );
  678. }
  679. }
  680. /**
  681. * Sets the global state to as if a given URL has been requested.
  682. *
  683. * This sets:
  684. * - The super globals.
  685. * - The globals.
  686. * - The query variables.
  687. * - The main query.
  688. *
  689. * @since 3.5.0
  690. *
  691. * @param string $url The URL for the request.
  692. */
  693. public function go_to( $url ) {
  694. /*
  695. * Note: the WP and WP_Query classes like to silently fetch parameters
  696. * from all over the place (globals, GET, etc), which makes it tricky
  697. * to run them more than once without very carefully clearing everything.
  698. */
  699. $_GET = array();
  700. $_POST = array();
  701. foreach ( array( 'query_string', 'id', 'postdata', 'authordata', 'day', 'currentmonth', 'page', 'pages', 'multipage', 'more', 'numpages', 'pagenow', 'current_screen' ) as $v ) {
  702. if ( isset( $GLOBALS[ $v ] ) ) {
  703. unset( $GLOBALS[ $v ] );
  704. }
  705. }
  706. $parts = parse_url( $url );
  707. if ( isset( $parts['scheme'] ) ) {
  708. $req = isset( $parts['path'] ) ? $parts['path'] : '';
  709. if ( isset( $parts['query'] ) ) {
  710. $req .= '?' . $parts['query'];
  711. // Parse the URL query vars into $_GET.
  712. parse_str( $parts['query'], $_GET );
  713. }
  714. } else {
  715. $req = $url;
  716. }
  717. if ( ! isset( $parts['query'] ) ) {
  718. $parts['query'] = '';
  719. }
  720. $_SERVER['REQUEST_URI'] = $req;
  721. unset( $_SERVER['PATH_INFO'] );
  722. self::flush_cache();
  723. unset( $GLOBALS['wp_query'], $GLOBALS['wp_the_query'] );
  724. $GLOBALS['wp_the_query'] = new WP_Query();
  725. $GLOBALS['wp_query'] = $GLOBALS['wp_the_query'];
  726. $public_query_vars = $GLOBALS['wp']->public_query_vars;
  727. $private_query_vars = $GLOBALS['wp']->private_query_vars;
  728. $GLOBALS['wp'] = new WP();
  729. $GLOBALS['wp']->public_query_vars = $public_query_vars;
  730. $GLOBALS['wp']->private_query_vars = $private_query_vars;
  731. _cleanup_query_vars();
  732. $GLOBALS['wp']->main( $parts['query'] );
  733. }
  734. /**
  735. * Allows tests to be skipped on single or multisite installs by using @group annotations.
  736. *
  737. * This is a custom extension of the PHPUnit requirements handling.
  738. *
  739. * Contains legacy code for skipping tests that are associated with an open Trac ticket.
  740. * Core tests no longer support this behaviour.
  741. *
  742. * @since 3.5.0
  743. */
  744. protected function checkRequirements() {
  745. parent::checkRequirements();
  746. $annotations = $this->getAnnotations();
  747. $groups = array();
  748. if ( ! empty( $annotations['class']['group'] ) ) {
  749. $groups = array_merge( $groups, $annotations['class']['group'] );
  750. }
  751. if ( ! empty( $annotations['method']['group'] ) ) {
  752. $groups = array_merge( $groups, $annotations['method']['group'] );
  753. }
  754. if ( ! empty( $groups ) ) {
  755. if ( in_array( 'ms-required', $groups, true ) ) {
  756. $this->skipWithoutMultisite();
  757. }
  758. if ( in_array( 'ms-excluded', $groups, true ) ) {
  759. $this->skipWithMultisite();
  760. }
  761. }
  762. // Core tests no longer check against open Trac tickets,
  763. // but others using WP_UnitTestCase may do so.
  764. if ( defined( 'WP_RUN_CORE_TESTS' ) && WP_RUN_CORE_TESTS ) {
  765. return;
  766. }
  767. if ( WP_TESTS_FORCE_KNOWN_BUGS ) {
  768. return;
  769. }
  770. $tickets = PHPUnit_Util_Test::getTickets( get_class( $this ), $this->getName( false ) );
  771. foreach ( $tickets as $ticket ) {
  772. if ( is_numeric( $ticket ) ) {
  773. $this->knownWPBug( $ticket );
  774. } elseif ( 0 === strpos( $ticket, 'Plugin' ) ) {
  775. $ticket = substr( $ticket, 6 );
  776. if ( $ticket && is_numeric( $ticket ) ) {
  777. $this->knownPluginBug( $ticket );
  778. }
  779. }
  780. }
  781. }
  782. /**
  783. * Skips the current test if there is an open Trac ticket associated with it.
  784. *
  785. * @since 3.5.0
  786. *
  787. * @param int $ticket_id Ticket number.
  788. */
  789. public function knownWPBug( $ticket_id ) {
  790. if ( WP_TESTS_FORCE_KNOWN_BUGS || in_array( $ticket_id, self::$forced_tickets, true ) ) {
  791. return;
  792. }
  793. if ( ! TracTickets::isTracTicketClosed( 'https://core.trac.wordpress.org', $ticket_id ) ) {
  794. $this->markTestSkipped( sprintf( 'WordPress Ticket #%d is not fixed', $ticket_id ) );
  795. }
  796. }
  797. /**
  798. * Skips the current test if there is an open Unit Test Trac ticket associated with it.
  799. *
  800. * @since 3.5.0
  801. *
  802. * @deprecated No longer used since the Unit Test Trac was merged into the Core Trac.
  803. *
  804. * @param int $ticket_id Ticket number.
  805. */
  806. public function knownUTBug( $ticket_id ) {
  807. return;
  808. }
  809. /**
  810. * Skips the current test if there is an open Plugin Trac ticket associated with it.
  811. *
  812. * @since 3.5.0
  813. *
  814. * @param int $ticket_id Ticket number.
  815. */
  816. public function knownPluginBug( $ticket_id ) {
  817. if ( WP_TESTS_FORCE_KNOWN_BUGS || in_array( 'Plugin' . $ticket_id, self::$forced_tickets, true ) ) {
  818. return;
  819. }
  820. if ( ! TracTickets::isTracTicketClosed( 'https://plugins.trac.wordpress.org', $ticket_id ) ) {
  821. $this->markTestSkipped( sprintf( 'WordPress Plugin Ticket #%d is not fixed', $ticket_id ) );
  822. }
  823. }
  824. /**
  825. * Adds a Trac ticket number to the `$forced_tickets` property.
  826. *
  827. * @since 3.5.0
  828. *
  829. * @param int $ticket Ticket number.
  830. */
  831. public static function forceTicket( $ticket ) {
  832. self::$forced_tickets[] = $ticket;
  833. }
  834. /**
  835. * Custom preparations for the PHPUnit process isolation template.
  836. *
  837. * When restoring global state between tests, PHPUnit defines all the constants that were already defined, and then
  838. * includes included files. This does not work with WordPress, as the included files define the constants.
  839. *
  840. * This method defines the constants after including files.
  841. *
  842. * @param Text_Template $template The template to prepare.
  843. */
  844. public function prepareTemplate( Text_Template $template ) {
  845. $template->setVar( array( 'constants' => '' ) );
  846. $template->setVar( array( 'wp_constants' => PHPUnit_Util_GlobalState::getConstantsAsString() ) );
  847. parent::prepareTemplate( $template );
  848. }
  849. /**
  850. * Creates a unique temporary file name.
  851. *
  852. * The directory in which the file is created depends on the environment configuration.
  853. *
  854. * @since 3.5.0
  855. *
  856. * @return string|bool Path on success, else false.
  857. */
  858. public function temp_filename() {
  859. $tmp_dir = '';
  860. $dirs = array( 'TMP', 'TMPDIR', 'TEMP' );
  861. foreach ( $dirs as $dir ) {
  862. if ( isset( $_ENV[ $dir ] ) && ! empty( $_ENV[ $dir ] ) ) {
  863. $tmp_dir = $dir;
  864. break;
  865. }
  866. }
  867. if ( empty( $tmp_dir ) ) {
  868. $tmp_dir = get_temp_dir();
  869. }
  870. $tmp_dir = realpath( $tmp_dir );
  871. return tempnam( $tmp_dir, 'wpunit' );
  872. }
  873. /**
  874. * Checks each of the WP_Query is_* functions/properties against expected boolean value.
  875. *
  876. * Any properties that are listed by name as parameters will be expected to be true; all others are
  877. * expected to be false. For example, assertQueryTrue( 'is_single', 'is_feed' ) means is_single()
  878. * and is_feed() must be true and everything else must be false to pass.
  879. *
  880. * @since 2.5.0
  881. * @since 3.8.0 Moved from `Tests_Query_Conditionals` to `WP_UnitTestCase`.
  882. * @since 5.3.0 Formalized the existing `...$prop` parameter by adding it
  883. * to the function signature.
  884. *
  885. * @param string ...$prop Any number of WP_Query properties that are expected to be true for the current request.
  886. */
  887. public function assertQueryTrue( ...$prop ) {
  888. global $wp_query;
  889. $all = array(
  890. 'is_404',
  891. 'is_admin',
  892. 'is_archive',
  893. 'is_attachment',
  894. 'is_author',
  895. 'is_category',
  896. 'is_comment_feed',
  897. 'is_date',
  898. 'is_day',
  899. 'is_embed',
  900. 'is_feed',
  901. 'is_front_page',
  902. 'is_home',
  903. 'is_privacy_policy',
  904. 'is_month',
  905. 'is_page',
  906. 'is_paged',
  907. 'is_post_type_archive',
  908. 'is_posts_page',
  909. 'is_preview',
  910. 'is_robots',
  911. 'is_favicon',
  912. 'is_search',
  913. 'is_single',
  914. 'is_singular',
  915. 'is_tag',
  916. 'is_tax',
  917. 'is_time',
  918. 'is_trackback',
  919. 'is_year',
  920. );
  921. foreach ( $prop as $true_thing ) {
  922. $this->assertContains( $true_thing, $all, "Unknown conditional: {$true_thing}." );
  923. }
  924. $passed = true;
  925. $message = '';
  926. foreach ( $all as $query_thing ) {
  927. $result = is_callable( $query_thing ) ? call_user_func( $query_thing ) : $wp_query->$query_thing;
  928. if ( in_array( $query_thing, $prop, true ) ) {
  929. if ( ! $result ) {
  930. $message .= $query_thing . ' is false but is expected to be true. ' . PHP_EOL;
  931. $passed = false;
  932. }
  933. } elseif ( $result ) {
  934. $message .= $query_thing . ' is true but is expected to be false. ' . PHP_EOL;
  935. $passed = false;
  936. }
  937. }
  938. if ( ! $passed ) {
  939. $this->fail( $message );
  940. }
  941. }
  942. /**
  943. * Selectively deletes a file.
  944. *
  945. * Does not delete a file if its path is set in the `$ignore_files` property.
  946. *
  947. * @param string $file File path.
  948. */
  949. public function unlink( $file ) {
  950. $exists = is_file( $file );
  951. if ( $exists && ! in_array( $file, self::$ignore_files, true ) ) {
  952. //error_log( $file );
  953. unlink( $file );
  954. } elseif ( ! $exists ) {
  955. $this->fail( "Trying to delete a file that doesn't exist: $file" );
  956. }
  957. }
  958. /**
  959. * Selectively deletes files from a directory.
  960. *
  961. * Does not delete files if their paths are set in the `$ignore_files` property.
  962. *
  963. * @param string $path Directory path.
  964. */
  965. public function rmdir( $path ) {
  966. $files = $this->files_in_dir( $path );
  967. foreach ( $files as $file ) {
  968. if ( ! in_array( $file, self::$ignore_files, true ) ) {
  969. $this->unlink( $file );
  970. }
  971. }
  972. }
  973. /**
  974. * Deletes files added to the `uploads` directory during tests.
  975. *
  976. * This method works in tandem with the `setUp()` and `rmdir()` methods:
  977. * - `setUp()` scans the `uploads` directory before every test, and stores its contents inside of the
  978. * `$ignore_files` property.
  979. * - `rmdir()` and its helper methods only delete files that are not listed in the `$ignore_files` property. If
  980. * called during `tearDown()` in tests, this will only delete files added during the previously run test.
  981. */
  982. public function remove_added_uploads() {
  983. $uploads = wp_upload_dir();
  984. $this->rmdir( $uploads['basedir'] );
  985. }
  986. /**
  987. * Returns a list of all files contained inside a directory.
  988. *
  989. * @since 4.0.0
  990. *
  991. * @param string $dir Path to the directory to scan.
  992. * @return array List of file paths.
  993. */
  994. public function files_in_dir( $dir ) {
  995. $files = array();
  996. $iterator = new RecursiveDirectoryIterator( $dir );
  997. $objects = new RecursiveIteratorIterator( $iterator );
  998. foreach ( $objects as $name => $object ) {
  999. if ( is_file( $name ) ) {
  1000. $files[] = $name;
  1001. }
  1002. }
  1003. return $files;
  1004. }
  1005. /**
  1006. * Returns a list of all files contained inside the `uploads` directory.
  1007. *
  1008. * @since 4.0.0
  1009. *
  1010. * @return array List of file paths.
  1011. */
  1012. public function scan_user_uploads() {
  1013. static $files = array();
  1014. if ( ! empty( $files ) ) {
  1015. return $files;
  1016. }
  1017. $uploads = wp_upload_dir();
  1018. $files = $this->files_in_dir( $uploads['basedir'] );
  1019. return $files;
  1020. }
  1021. /**
  1022. * Deletes all directories contained inside a directory.
  1023. *
  1024. * @since 4.1.0
  1025. *
  1026. * @param string $path Path to the directory to scan.
  1027. */
  1028. public function delete_folders( $path ) {
  1029. $this->matched_dirs = array();
  1030. if ( ! is_dir( $path ) ) {
  1031. return;
  1032. }
  1033. $this->scandir( $path );
  1034. foreach ( array_reverse( $this->matched_dirs ) as $dir ) {
  1035. rmdir( $dir );
  1036. }
  1037. rmdir( $path );
  1038. }
  1039. /**
  1040. * Retrieves all directories contained inside a directory and stores them in the `$matched_dirs` property. Hidden
  1041. * directories are ignored.
  1042. *
  1043. * This is a helper for the `delete_folders()` method.
  1044. *
  1045. * @since 4.1.0
  1046. *
  1047. * @param string $dir Path to the directory to scan.
  1048. */
  1049. public function scandir( $dir ) {
  1050. foreach ( scandir( $dir ) as $path ) {
  1051. if ( 0 !== strpos( $path, '.' ) && is_dir( $dir . '/' . $path ) ) {
  1052. $this->matched_dirs[] = $dir . '/' . $path;
  1053. $this->scandir( $dir . '/' . $path );
  1054. }
  1055. }
  1056. }
  1057. /**
  1058. * Converts a microtime string into a float.
  1059. *
  1060. * @since 4.1.0
  1061. *
  1062. * @param string $microtime Time string generated by `microtime()`.
  1063. * @return float `microtime()` output as a float.
  1064. */
  1065. protected function _microtime_to_float( $microtime ) {
  1066. $time_array = explode( ' ', $microtime );
  1067. return array_sum( $time_array );
  1068. }
  1069. /**
  1070. * Deletes a user from the database in a Multisite-agnostic way.
  1071. *
  1072. * @since 4.3.0
  1073. *
  1074. * @param int $user_id User ID.
  1075. * @return bool True if the user was deleted.
  1076. */
  1077. public static function delete_user( $user_id ) {
  1078. if ( is_multisite() ) {
  1079. return wpmu_delete_user( $user_id );
  1080. }
  1081. return wp_delete_user( $user_id );
  1082. }
  1083. /**
  1084. * Resets permalinks and flushes rewrites.
  1085. *
  1086. * @since 4.4.0
  1087. *
  1088. * @global WP_Rewrite $wp_rewrite
  1089. *
  1090. * @param string $structure Optional. Permalink structure to set. Default empty.
  1091. */
  1092. public function set_permalink_structure( $structure = '' ) {
  1093. global $wp_rewrite;
  1094. $wp_rewrite->init();
  1095. $wp_rewrite->set_permalink_structure( $structure );
  1096. $wp_rewrite->flush_rules();
  1097. }
  1098. /**
  1099. * Creates an attachment post from an uploaded file.
  1100. *
  1101. * @since 4.4.0
  1102. *
  1103. * @param array $upload Array of information about the uploaded file, provided by wp_upload_bits().
  1104. * @param int $parent_post_id Optional. Parent post ID.
  1105. * @return int|WP_Error The attachment ID on success. The value 0 or WP_Error on failure.
  1106. */
  1107. public function _make_attachment( $upload, $parent_post_id = 0 ) {
  1108. $type = '';
  1109. if ( ! empty( $upload['type'] ) ) {
  1110. $type = $upload['type'];
  1111. } else {
  1112. $mime = wp_check_filetype( $upload['file'] );
  1113. if ( $mime ) {
  1114. $type = $mime['type'];
  1115. }
  1116. }
  1117. $attachment = array(
  1118. 'post_title' => wp_basename( $upload['file'] ),
  1119. 'post_content' => '',
  1120. 'post_type' => 'attachment',
  1121. 'post_parent' => $parent_post_id,
  1122. 'post_mime_type' => $type,
  1123. 'guid' => $upload['url'],
  1124. );
  1125. $id = wp_insert_attachment( $attachment, $upload['file'], $parent_post_id );
  1126. wp_update_attachment_metadata( $id, wp_generate_attachment_metadata( $id, $upload['file'] ) );
  1127. return $id;
  1128. }
  1129. /**
  1130. * Updates the modified and modified GMT date of a post in the database.
  1131. *
  1132. * @since 4.8.0
  1133. *
  1134. * @global wpdb $wpdb WordPress database abstraction object.
  1135. *
  1136. * @param int $post_id Post ID.
  1137. * @param string $date Post date, in the format YYYY-MM-DD HH:MM:SS.
  1138. * @return int|false 1 on success, or false on error.
  1139. */
  1140. protected function update_post_modified( $post_id, $date ) {
  1141. global $wpdb;
  1142. return $wpdb->update(
  1143. $wpdb->posts,
  1144. array(
  1145. 'post_modified' => $date,
  1146. 'post_modified_gmt' => $date,
  1147. ),
  1148. array(
  1149. 'ID' => $post_id,
  1150. ),
  1151. array(
  1152. '%s',
  1153. '%s',
  1154. ),
  1155. array(
  1156. '%d',
  1157. )
  1158. );
  1159. }
  1160. /**
  1161. * Touches the given file and its directory if it doesn't already exist.
  1162. *
  1163. * This can be used to ensure a file that is implictly relied on in a test exists
  1164. * without it having to be built.
  1165. *
  1166. * @param string $file The file name.
  1167. */
  1168. public static function touch( $file ) {
  1169. if ( file_exists( $file ) ) {
  1170. return;
  1171. }
  1172. $dir = dirname( $file );
  1173. if ( ! file_exists( $dir ) ) {
  1174. mkdir( $dir, 0777, true );
  1175. }
  1176. touch( $file );
  1177. }
  1178. }