3 namespace Wikimedia\Purtle\Tests;
5 use PHPUnit_Framework_TestCase;
6 use Wikimedia\Purtle\RdfWriter;
9 * Base class for tests for RdfWriter implementations.
10 * Provides a common test suite for all implementations.
13 * @author Daniel Kinzler
14 * @author Thiemo Mättig
16 abstract class RdfWriterTestBase extends PHPUnit_Framework_TestCase {
18 abstract protected function getFileSuffix();
23 abstract protected function newWriter();
25 public function testGetMimeType() {
26 $mimeType = $this->newWriter()->getMimeType();
27 $this->assertInternalType( 'string', $mimeType );
28 $this->assertRegExp( '/^\w+\/[\w-]+(\+xml)?(; charset=UTF-8)?$/', $mimeType );
31 public function testTriples() {
32 $writer = $this->newWriter();
34 $writer->prefix( 'acme', 'http://acme.test/' );
37 $writer->about( 'http://foobar.test/Bananas' )
38 ->say( 'a' )->is( 'http://foobar.test/Fruit' ); // shorthand name "a"
40 $writer->about( 'acme', 'Nuts' )
41 ->say( 'acme', 'weight' )->value( '5.5', 'xsd', 'decimal' );
43 // redundant about( 'acme', 'Nuts' )
44 $writer->about( 'acme', 'Nuts' )
45 ->say( 'acme', 'color' )->value( 'brown' );
48 $rdf = $writer->drain();
49 $this->assertOutputLines( 'Triples', $rdf );
52 public function testPredicates() {
53 $writer = $this->newWriter();
55 $writer->prefix( '', 'http://acme.test/' ); // empty prefix
58 $writer->about( 'http://foobar.test/Bananas' )
59 ->a( 'http://foobar.test/Fruit' ) // shorthand function a()
60 ->say( '', 'name' ) // empty prefix
62 ->say( '', 'name' ) // redundant say( '', 'name' )
63 ->text( 'Banane', 'de' );
65 $writer->about( 'http://foobar.test/Apples' )
66 ->say( '', 'name' ) // subsequent call to say( '', 'name' ) for a different subject
70 $rdf = $writer->drain();
71 $this->assertOutputLines( 'Predicates', $rdf );
74 public function testPredicates_drain() {
75 $writer = $this->newWriter();
77 $writer->prefix( '', 'http://acme.test/' ); // empty prefix
80 $writer->about( 'http://foobar.test/Bananas' )
81 ->a( 'http://foobar.test/Fruit' ) // shorthand function a()
82 ->say( '', 'name' ) // empty prefix
84 ->say( '', 'name' ) // redundant say( '', 'name' )
85 ->text( 'Banane', 'de' );
87 $rdf1 = $writer->drain();
88 $this->assertNotEmpty( $rdf1 );
90 $writer->about( 'http://foobar.test/Apples' )
91 ->say( '', 'name' ) // subsequent call to say( '', 'name' ) for a different subject
95 $rdf2 = $writer->drain();
96 $this->assertNotEmpty( $rdf2 );
98 $this->assertOutputLines( 'Predicates', $rdf1 . $rdf2 );
101 public function testPredicates_sub() {
102 $writer = $this->newWriter();
104 $writer->prefix( '', 'http://acme.test/' ); // empty prefix
107 $sub = $writer->sub();
109 // output of the sub writer will appear after the output of the main writer.
110 $sub->about( 'http://foobar.test/Apples' )
111 ->say( '', 'name' ) // subsequent call to say( '', 'name' ) for a different subject
114 $writer->about( 'http://foobar.test/Bananas' )
115 ->a( 'http://foobar.test/Fruit' ) // shorthand function a()
116 ->say( '', 'name' ) // empty prefix
118 ->say( '', 'name' ) // redundant say( '', 'name' )
119 ->text( 'Banane', 'de' );
123 $rdf = $writer->drain();
124 $this->assertOutputLines( 'Predicates', $rdf );
127 public function testPredicates_sub_drain() {
128 $writer = $this->newWriter();
130 $writer->prefix( '', 'http://acme.test/' ); // empty prefix
133 $sub = $writer->sub();
135 $writer->about( 'http://foobar.test/Bananas' )
136 ->a( 'http://foobar.test/Fruit' ) // shorthand function a()
137 ->say( '', 'name' ) // empty prefix
139 ->say( '', 'name' ) // redundant say( '', 'name' )
140 ->text( 'Banane', 'de' );
142 $rdf1 = $writer->drain();
143 $this->assertNotEmpty( $rdf1 );
145 // sub-writer should still be usable after drain()
146 $sub->about( 'http://foobar.test/Apples' )
147 ->say( '', 'name' ) // subsequent call to say( '', 'name' ) for a different subject
152 $rdf2 = $writer->drain();
153 $this->assertNotEmpty( $rdf2 );
155 $this->assertOutputLines( 'Predicates', $rdf1 . $rdf2 );
158 public function testValues() {
159 $writer = $this->newWriter();
161 $writer->prefix( 'acme', 'http://acme.test/' );
164 $writer->about( 'http://foobar.test/Bananas' )
165 ->say( 'acme', 'multi' )
169 ->say( 'acme', 'type' )
170 ->value( 'foo', 'acme', 'thing' )
171 ->value( '-5', 'xsd', 'integer' )
172 ->value( '-5', 'xsd', 'decimal' )
173 ->value( '-5', 'xsd', 'double' )
174 ->value( 'true', 'xsd', 'boolean' )
175 ->value( 'false', 'xsd', 'boolean' )
176 ->say( 'acme', 'autotype' )
181 ->say( 'acme', 'no-autotype' )
182 ->value( -5, 'xsd', 'decimal' )
183 ->value( 3.14, 'xsd', 'string' )
184 ->value( true, 'xsd', 'string' )
185 ->value( false, 'xsd', 'string' )
186 ->say( 'acme', 'shorthand' )->value( 'foo' )
187 ->say( 'acme', 'typed-shorthand' )->value( 'foo', 'acme', 'thing' );
190 $rdf = $writer->drain();
191 $this->assertOutputLines( 'Values', $rdf );
194 public function testResources() {
195 $writer = $this->newWriter();
197 $writer->prefix( 'acme', 'http://acme.test/' );
200 $writer->about( 'acme', 'Bongos' )
201 ->say( 'acme', 'sounds' )
202 ->is( 'acme', 'Bing' )
203 ->is( 'http://foobar.test/sound/Bang' );
206 $rdf = $writer->drain();
207 $this->assertOutputLines( 'Resources', $rdf );
210 public function testTexts() {
211 $writer = $this->newWriter();
213 $writer->prefix( 'acme', 'http://acme.test/' );
216 $writer->about( 'acme', 'Bongos' )
217 ->say( 'acme', 'sounds' )
218 ->text( 'Bom', 'de' )
219 ->text( 'Bam', 'en' )
220 ->text( 'Como estas', 'es-419' )
221 ->text( 'What?', 'bad tag' );
224 $rdf = $writer->drain();
225 $this->assertOutputLines( 'Texts', $rdf );
228 public function testNumbers() {
229 $writer = $this->newWriter();
231 $writer->prefix( 'acme', 'http://acme.test/' );
234 $writer->about( 'acme', 'Bongos' )
235 ->say( 'acme', 'stock' )->value( 5, 'xsd', 'integer' )
237 ->about( 'acme', 'Tablas' )
238 ->say( 'acme', 'stock' )->value( 6 );
241 $rdf = $writer->drain();
242 $this->assertOutputLines( 'Numbers', $rdf );
245 public function testEricMiller() {
246 // exampel taken from http://www.w3.org/2007/02/turtle/primer/
248 $writer = $this->newWriter();
250 $writer->prefix( 'contact', 'http://www.w3.org/2000/10/swap/pim/contact#' );
253 $writer->about( 'http://www.w3.org/People/EM/contact#me' )
254 ->say( 'rdf', 'type' )->is( 'contact', 'Person' )
255 ->say( 'contact', 'fullName' )->text( 'Eric Miller' )
256 ->say( 'contact', 'mailbox' )->is( 'mailto:em@w3.org' )
257 ->say( 'contact', 'personalTitle' )->text( 'Dr.' );
260 $rdf = $writer->drain();
261 $this->assertOutputLines( 'EricMiller', $rdf );
264 public function testLabeledBlankNode() {
265 // exampel taken from http://www.w3.org/2007/02/turtle/primer/
267 $writer = $this->newWriter();
269 $writer->prefix( 'exterms', 'http://www.example.org/terms/' );
270 $writer->prefix( 'exstaff', 'http://www.example.org/staffid/' );
273 $writer->about( 'exstaff', '85740' )
274 ->say( 'exterms', 'address' )->is( '_', $label = $writer->blank( 'johnaddress' ) )
275 ->about( '_', $label )
276 ->say( 'exterms', 'street' )->text( '1501 Grant Avenue' )
277 ->say( 'exterms', 'city' )->text( 'Bedfort' )
278 ->say( 'exterms', 'state' )->text( 'Massachusetts' )
279 ->say( 'exterms', 'postalCode' )->text( '01730' );
282 $rdf = $writer->drain();
283 $this->assertOutputLines( 'LabeledBlankNode', $rdf );
286 public function testNumberedBlankNodes() {
287 // exampel taken from http://www.w3.org/2007/02/turtle/primer/
289 $writer = $this->newWriter();
291 $writer->prefix( 'exterms', 'http://www.example.org/terms/' );
292 $writer->prefix( 'exstaff', 'http://www.example.org/staffid/' );
293 $writer->prefix( 'ex', 'http://example.org/packages/vocab#' );
296 $writer->about( 'exstaff', 'Sue' )
297 ->say( 'exterms', 'publication' )->is( '_', $label1 = $writer->blank() );
298 $writer->about( '_', $label1 )
299 ->say( 'exterms', 'title' )->text( 'Antology of Time' );
301 $writer->about( 'exstaff', 'Jack' )
302 ->say( 'exterms', 'publication' )->is( '_', $label2 = $writer->blank() );
303 $writer->about( '_', $label2 )
304 ->say( 'exterms', 'title' )->text( 'Anthony of Time' );
307 $rdf = $writer->drain();
308 $this->assertOutputLines( 'NumberedBlankNode', $rdf );
311 public function testQuotesAndSpecials() {
312 $writer = $this->newWriter();
313 $writer->prefix( 'exterms', 'http://www.example.org/terms/' );
316 $writer->about( 'exterms', 'Duck' )->say( 'exterms', 'says' )
317 ->text( 'Duck says: "Quack!"' );
318 $writer->about( 'exterms', 'Cow' )->say( 'exterms', 'says' )
319 ->text( "Cow says:\n\r 'Moo! \\Moo!'" );
320 $writer->about( 'exterms', 'Bear' )->say( 'exterms', 'says' )
321 ->text( 'Bear says: Превед!' );
323 $rdf = $writer->drain();
324 $this->assertOutputLines( 'TextWithSpecialChars', $rdf );
328 * @param string $datasetName
329 * @param string[]|string $actual
331 private function assertOutputLines( $datasetName, $actual ) {
332 $path = __DIR__ . '/../data/' . $datasetName . '.' . $this->getFileSuffix();
334 $this->assertNTriplesEquals(
335 file_get_contents( $path ),
337 "Result mismatches data in $path"
342 * @param string[]|string $nTriples
344 * @return string[] Sorted alphabetically.
346 protected function normalizeNTriples( $nTriples ) {
347 if ( is_string( $nTriples ) ) {
348 // Trim and ignore newlines at the end of the file only.
349 $nTriples = explode( "\n", rtrim( $nTriples, "\n" ) );
358 * @param string[]|string $expected
359 * @param string[]|string $actual
360 * @param string $message
362 protected function assertNTriplesEquals( $expected, $actual, $message = '' ) {
363 $expected = $this->normalizeNTriples( $expected );
364 $actual = $this->normalizeNTriples( $actual );
366 // Comparing $expected and $actual directly would show triples that are present in both but
367 // shifted in position. That makes the output hard to read. Calculating the $missing and
368 // $extra sets helps.
369 $extra = array_diff( $actual, $expected );
370 $missing = array_diff( $expected, $actual );
372 // Cute: $missing and $extra can be equal only if they are empty. Comparing them here
373 // directly looks a bit odd in code, but produces meaningful output, especially if the input
375 $this->assertEquals( $missing, $extra, $message );
378 //FIXME: test non-ascii literals!
379 //FIXME: test uerl-encoding