summaryrefslogtreecommitdiff
path: root/www/wiki/extensions/Scribunto/tests/phpunit/engines/LuaSandbox/SandboxTest.php
blob: db80f5cfe95477fe64a82089231c86c319af9f5c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
<?php

// @codingStandardsIgnoreLine Squiz.Classes.ValidClassName.NotCamelCaps
class Scribunto_LuaSandboxTest extends Scribunto_LuaEngineTestBase {
	protected static $moduleName = 'SandboxTests';

	public static function suite( $className ) {
		return self::makeSuite( $className, 'LuaSandbox' );
	}

	protected function getTestModules() {
		return parent::getTestModules() + [
			'SandboxTests' => __DIR__ . '/SandboxTests.lua',
		];
	}

	public function testArgumentParsingTime() {
		if ( !wfGetRusage() ) {
			$this->markTestSkipped( "getrusage is not available" );
		}

		$engine = $this->getEngine();
		$parser = $engine->getParser();
		$pp = $parser->getPreprocessor();
		$frame = $pp->newFrame();

		$parser->setHook( 'scribuntodelay', function () {
			$endTime = $this->getRuTime() + 0.5;

			// Waste CPU cycles
			do {
				$t = $this->getRuTime();
			} while ( $t < $endTime );

			return "ok";
		} );
		$this->extraModules['Module:TestArgumentParsingTime'] = '
			return {
				f = function ( frame )
					return frame.args[1]
				end,
				f2 = function ( frame )
					return frame:preprocess( "{{#invoke:TestArgumentParsingTime|f|}}" )
				end,
				f3 = function ( frame )
					return frame:preprocess( "{{#invoke:TestArgumentParsingTime|f|<scribuntodelay/>}}" )
				end,
			}
		';

		// Below we assert that the CPU time counted by LuaSandbox is $delta less than
		// the CPU time actually spent.
		// That way we can make sure that the time spent in the parser hook (which
		// must be more than delta) is not taken into account.
		$delta = 0.25;

		$u0 = $engine->getInterpreter()->getCPUUsage();
		$uTimeBefore = $this->getRuTime();
		$frame->expand(
			$pp->preprocessToObj(
				'{{#invoke:TestArgumentParsingTime|f|<scribuntodelay/>}}'
			)
		);
		$threshold = $this->getRuTime() - $uTimeBefore - $delta;
		$this->assertLessThan( $threshold, $engine->getInterpreter()->getCPUUsage() - $u0,
			'Argument access time was not counted'
		);

		$uTimeBefore = $this->getRuTime();
		$u0 = $engine->getInterpreter()->getCPUUsage();
		$frame->expand(
			$pp->preprocessToObj(
				'{{#invoke:TestArgumentParsingTime|f2|<scribuntodelay/>}}'
			)
		);
		$threshold = $this->getRuTime() - $uTimeBefore - $delta;
		$this->assertLessThan( $threshold, $engine->getInterpreter()->getCPUUsage() - $u0,
			'Unused arguments not counted in preprocess'
		);

		$uTimeBefore = $this->getRuTime();
		$u0 = $engine->getInterpreter()->getCPUUsage();
		$frame->expand(
			$pp->preprocessToObj(
				'{{#invoke:TestArgumentParsingTime|f3}}'
			)
		);
		$threshold = $this->getRuTime() - $uTimeBefore - $delta;
		// If the underlying node is extremely slow, this test might produce false positives
		$this->assertGreaterThan( $threshold, $engine->getInterpreter()->getCPUUsage() - $u0,
			'Recursive argument access time was counted'
		);
	}

	private function getRuTime() {
		$ru = wfGetRusage();
		return $ru['ru_utime.tv_sec'] + $ru['ru_utime.tv_usec'] / 1e6 +
			$ru['ru_stime.tv_sec'] + $ru['ru_stime.tv_usec'] / 1e6;
	}

}