Completed
Push — master ( 1bcd75...700cf9 )
by Hamish
11:32
created

DatabaseTest::testTransactions()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 36
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 36
rs 8.8571
cc 3
eloc 28
nc 3
nop 0
1
<?php
2
/**
3
 * @package framework
4
 * @subpackage Testing
5
 */
6
class DatabaseTest extends SapphireTest {
7
8
	protected $extraDataObjects = array(
9
		'DatabaseTest_MyObject',
10
	);
11
12
	protected $usesDatabase = true;
13
14
	public function testDontRequireField() {
15
		$schema = DB::get_schema();
16
		$this->assertArrayHasKey(
17
			'MyField',
18
			$schema->fieldList('DatabaseTest_MyObject')
19
		);
20
21
		$schema->dontRequireField('DatabaseTest_MyObject', 'MyField');
22
23
		$this->assertArrayHasKey(
24
			'_obsolete_MyField',
25
			$schema->fieldList('DatabaseTest_MyObject'),
26
			'Field is renamed to _obsolete_<fieldname> through dontRequireField()'
27
		);
28
29
		$this->resetDBSchema(true);
30
	}
31
32
	public function testRenameField() {
33
		$schema = DB::get_schema();
34
35
		$schema->clearCachedFieldlist();
36
37
		$schema->renameField('DatabaseTest_MyObject', 'MyField', 'MyRenamedField');
38
39
		$this->assertArrayHasKey(
40
			'MyRenamedField',
41
			$schema->fieldList('DatabaseTest_MyObject'),
42
			'New fieldname is set through renameField()'
43
		);
44
		$this->assertArrayNotHasKey(
45
			'MyField',
46
			$schema->fieldList('DatabaseTest_MyObject'),
47
			'Old fieldname isnt preserved through renameField()'
48
		);
49
50
		$this->resetDBSchema(true);
51
	}
52
53
	public function testMySQLCreateTableOptions() {
54
		if(!(DB::get_conn() instanceof MySQLDatabase)) {
55
			$this->markTestSkipped('MySQL only');
56
		}
57
58
59
		$ret = DB::query(sprintf(
60
			'SHOW TABLE STATUS WHERE "Name" = \'%s\'',
61
			'DatabaseTest_MyObject'
62
		))->first();
63
		$this->assertEquals($ret['Engine'],'InnoDB',
64
			"MySQLDatabase tables can be changed to InnoDB through DataObject::\$create_table_options"
65
		);
66
	}
67
68
	function testIsSchemaUpdating() {
69
		$schema = DB::get_schema();
70
71
		$this->assertFalse($schema->isSchemaUpdating(), 'Before the transaction the flag is false.');
72
73
		// Test complete schema update
74
		$test = $this;
75
		$schema->schemaUpdate(function() use ($test, $schema) {
76
			$test->assertTrue($schema->isSchemaUpdating(), 'During the transaction the flag is true.');
77
		});
78
		$this->assertFalse($schema->isSchemaUpdating(), 'After the transaction the flag is false.');
79
80
		// Test cancelled schema update
81
		$schema->schemaUpdate(function() use ($test, $schema) {
82
			$schema->cancelSchemaUpdate();
83
			$test->assertFalse($schema->doesSchemaNeedUpdating(), 'After cancelling the transaction the flag is false');
84
		});
85
	}
86
87
	public function testSchemaUpdateChecking() {
88
		$schema = DB::get_schema();
89
90
		// Initially, no schema changes necessary
91
		$test = $this;
92
		$schema->schemaUpdate(function() use ($test, $schema) {
93
			$test->assertFalse($schema->doesSchemaNeedUpdating());
94
95
			// If we make a change, then the schema will need updating
96
			$schema->transCreateTable("TestTable");
97
			$test->assertTrue($schema->doesSchemaNeedUpdating());
98
99
			// If we make cancel the change, then schema updates are no longer necessary
100
			$schema->cancelSchemaUpdate();
101
			$test->assertFalse($schema->doesSchemaNeedUpdating());
102
		});
103
	}
104
105
	public function testHasTable() {
106
		$this->assertTrue(DB::get_schema()->hasTable('DatabaseTest_MyObject'));
107
		$this->assertFalse(DB::get_schema()->hasTable('asdfasdfasdf'));
108
	}
109
110
	public function testGetAndReleaseLock() {
111
		$db = DB::get_conn();
112
113
		if(!$db->supportsLocks()) {
114
			return $this->markTestSkipped('Tested database doesn\'t support application locks');
115
		}
116
117
		$this->assertTrue($db->getLock('DatabaseTest'),
118
			'Can aquire lock');
119
		// $this->assertFalse($db->getLock('DatabaseTest'), 'Can\'t repeatedly aquire the same lock');
120
		$this->assertTrue($db->getLock('DatabaseTest'),
121
			'The same lock can be aquired multiple times in the same connection');
122
123
		$this->assertTrue($db->getLock('DatabaseTestOtherLock'),
124
			'Can aquire different lock');
125
		$db->releaseLock('DatabaseTestOtherLock');
126
127
		// Release potentially stacked locks from previous getLock() invocations
128
		$db->releaseLock('DatabaseTest');
129
		$db->releaseLock('DatabaseTest');
130
131
		$this->assertTrue($db->getLock('DatabaseTest'),
132
			'Can aquire lock after releasing it');
133
		$db->releaseLock('DatabaseTest');
134
	}
135
136
	public function testCanLock() {
137
		$db = DB::get_conn();
138
139
		if(!$db->supportsLocks()) {
140
			return $this->markTestSkipped('Database doesn\'t support locks');
141
		}
142
143
		if($db instanceof MSSQLDatabase) {
0 ignored issues
show
Bug introduced by
The class MSSQLDatabase does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
144
			return $this->markTestSkipped('MSSQLDatabase doesn\'t support inspecting locks');
145
		}
146
147
		$this->assertTrue($db->canLock('DatabaseTest'), 'Can lock before first aquiring one');
148
		$db->getLock('DatabaseTest');
149
		$this->assertFalse($db->canLock('DatabaseTest'), 'Can\'t lock after aquiring one');
150
		$db->releaseLock('DatabaseTest');
151
		$this->assertTrue($db->canLock('DatabaseTest'), 'Can lock again after releasing it');
152
	}
153
154
	public function testTransactions() {
155
		$conn = DB::getConn();
0 ignored issues
show
Deprecated Code introduced by
The method DB::getConn() has been deprecated with message: since version 4.0 Use DB::get_conn instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
156
		if(!$conn->supportsTransactions()) {
157
			$this->markTestSkipped("DB Doesn't support transactions");
158
			return;
159
		}
160
161
		// Test that successful transactions are comitted
162
		$obj = new DatabaseTest_MyObject();
163
		$failed = false;
164
		$conn->withTransaction(function() use (&$obj) {
165
			$obj->MyField = 'Save 1';
0 ignored issues
show
Documentation introduced by
The property MyField does not exist on object<DatabaseTest_MyObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
166
			$obj->write();
167
		}, function() use (&$failed) {
168
			$failed = true;
169
		});
170
		$this->assertEquals('Save 1', DatabaseTest_MyObject::get()->first()->MyField);
171
		$this->assertFalse($failed);
172
173
		// Test failed transactions are rolled back
174
		$ex = null;
175
		$failed = false;
176
		try {
177
			$conn->withTransaction(function() use (&$obj) {
178
				$obj->MyField = 'Save 2';
0 ignored issues
show
Documentation introduced by
The property MyField does not exist on object<DatabaseTest_MyObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
179
				$obj->write();
180
				throw new Exception("error");
181
			}, function() use (&$failed) {
182
				$failed = true;
183
			});
184
		} catch ( Exception $ex) {}
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
185
		$this->assertTrue($failed);
186
		$this->assertEquals('Save 1', DatabaseTest_MyObject::get()->first()->MyField);
187
		$this->assertInstanceOf('Exception', $ex);
188
		$this->assertEquals('error', $ex->getMessage());
189
	}
190
191
192
}
193
194
class DatabaseTest_MyObject extends DataObject implements TestOnly {
195
196
	private static $create_table_options = array(MySQLSchemaManager::ID => 'ENGINE=InnoDB');
197
198
	private static $db = array(
199
		'MyField' => 'Varchar'
200
	);
201
}
202