<?php /** * Test methods for the P4 Change class. * * @copyright 2012 Perforce Software. All rights reserved. * @license Please see LICENSE.txt in top-level folder of this distribution. * @version <release>/<patch> */ namespace P4Test\Spec; use P4Test\TestCase; use P4\Connection\Connection; use P4\Connection\Exception\CommandException; use P4\Connection\Exception\ConflictException; use P4\File\File; use P4\File\Query as FileQuery; use P4\Log\Logger; use P4\Model\Fielded\Iterator as FieldedIterator; use P4\Spec\Client; use P4\Spec\Change; use P4\Spec\Definition; use P4\Spec\Exception\NotFoundException; use P4\Spec\Exception\UnopenedException; use P4\Spec\Exception\Exception as SpecException; use P4\Spec\Job; use P4\Spec\User; use Zend\Log\Logger as ZendLogger; use Zend\Log\Writer\Mock as MockLog; class ChangeTest extends TestCase { /** * Test instantiation. */ public function testInstantiation() { $change = new Change; $this->assertTrue($change instanceof Change, 'Expected class'); $originalConnection = $change->getConnection(); // test setting connection via constructor. $p4 = Connection::factory(); $change = new Change($p4); $this->assertSame( $p4, $change->getConnection(), 'getConnection() should match connection passed to constructor.' ); // ensure two connections differ. $this->assertNotSame( $change->getConnection(), $originalConnection, 'Connections should not match.' ); } /** * Test exists. */ public function testExists() { // ensure id-exists returns false for non-existant change // (we have no changes yet) $this->assertFalse(Change::exists(123), 'Given change should not exist.'); // test that a bogus id does not exist $this->assertFalse(Change::exists('*'), 'Bogus change should not exist.'); // ensure default change exists. $this->assertTrue(Change::exists('default'), 'Default change should exist.'); // create a change and ensure it exists. $change = new Change; $change->setDescription('this is a test'); $change->save(); // new change should have id of 1. $this->assertTrue($change->getId() === 1, 'Change number should be one.'); // change number 1 should exist. $this->assertTrue(Change::exists(1), 'Change 1 should exist.'); $this->assertTrue(Change::exists('1'), 'Change "1" should exist.'); } /** * Test fetch. */ public function testFetch() { // ensure fetch fails for a non-existant change. try { Change::fetch(1234); $this->fail('Fetch should fail for a non-existant change.'); } catch (NotFoundException $e) { $this->assertTrue(true); } // ensure fetch succeeds for default change. try { Change::fetch('default'); $this->assertTrue(true); } catch (NotFoundException $e) { $this->fail('Fetch should succeed for default change.'); } // ensure fetch succeeds for numbered change. $change = new Change; $description = "this is a test\n"; $change->setDescription($description); $change->save(); try { $fetched = Change::fetch($change->getId()); $this->assertTrue(true); } catch (NotFoundException $e) { $this->fail('Fetch should succeed for a numbered change that exists.'); } // ensure it only takes one command to fetch a change and read // basic values from it -- we verify this by peeking at the log $original = Logger::hasLogger() ? Logger::getLogger() : null; $logger = new ZendLogger; $mock = new MockLog; $logger->addWriter($mock); Logger::setLogger($logger); $fetched = Change::fetch($change->getId()); $this->assertSame($description, $fetched->getDescription()); $this->assertInternalType('int', $fetched->getTime()); $this->assertSame(1, count($mock->events)); // restore original logger if there is one. Logger::setLogger($original); } /** * Test that there is no difference between a saved change and a fetched change. * * @todo test get files. */ public function testSavedVsFetched() { // open a file for add. $file = new File; $file->setFilespec('//depot/test-file') ->add(); // create a job. $job = new Job; $job->set('Description', 'fix something') ->save(); // save a change with a file and a job. $change = new Change; $change->setDescription("a change with a file and a job.\n") ->setFiles(array($file->getFilespec())) ->setJobs(array($job->getId())) ->save(); $fetched = Change::fetch($change->getId()); $types = array('Id', 'Date', 'User', 'Status', 'Description', 'JobStatus', 'Jobs'); foreach ($types as $type) { $method = "get$type"; $this->assertSame($fetched->$method(), $change->$method(), "Expect matching $type."); } } /** * Test the fetch all method. */ public function testFetchAll() { /* * FETCH_MAXIMUM - set to integer value to limit to the * first 'max' number of entries. * FETCH_BY_FILESPEC - set to a filespec to limit changes to those * affecting the file(s) matching the filespec. * FETCH_BY_STATUS - set to a valid change status to limit result * to changes with that status (e.g. 'pending'). * FETCH_INTEGRATED - set to true to include changes integrated * into the specified files. * FETCH_BY_CLIENT - set to a client to limit changes to those * on the named client. * FETCH_BY_USER - set to a user to limit changes to those * owned by the named user. * FETCH_AFTER - set to an id _after_ which to start collecting entries * note: entries seen before 'after' count towards max. */ // create a file and submitted change $file1 = new File; $file1->setFilespec('//depot/path-a/test-file')->add()->setLocalContents('test')->submit('test-1'); // create a file and submitted change $file2 = new File; $file2->setFilespec('//depot/path-b/test-file')->add()->setLocalContents('test')->submit('test-2'); // create a pending change with 2 files $files = new FieldedIterator; $file3 = new File; $files[] = $file3->setFilespec('//depot/path-c/test-file1') ->add() ->setLocalContents('test'); $file4 = new File; $files[] = $file4->setFilespec('//depot/path-c/test-file2') ->add() ->setLocalContents('test'); $change = new Change; $change->setFiles($files)->setDescription("Has 2 files\n")->save(); // create a change by another user, in another workspace. $user = new User; $password = 'AnotherPass'; $user->setId('alternate') ->setEmail('alternate@example.perforce.com') ->setFullName('Alternate User') ->save(); $client = new Client; $clientId = 'another-test-client'; $client->setId($clientId) ->setRoot(DATA_PATH . "/clients/$clientId") ->setView(array('//depot/... //another-test-client/...')) ->save(); $p4 = Connection::factory( $this->p4->getPort(), $user->getId(), $client->getId() ); $change = new Change($p4); $change->setDescription("in alternate client\n")->save(); // and have the alternate user integrate an existing file $integFilespec = '//depot/path-a/test-integ'; $result = $p4->run('integrate', array('-f', $file1->getFilespec(), $integFilespec)); $change = new Change($p4); $change->addFile($integFilespec)->setDescription('Integration')->submit(); // Testing begins: ensure correct number of changes returned. $changes = Change::fetchAll(); $this->assertEquals( 5, $changes->count(), 'There should be 5 changes.' ); // ensure that first change matches last submitted change. $expected = array( 'Change' => 5, 'Client' => $clientId, 'User' => $user->getId(), 'Status' => 'submitted', 'Description' => "Integration\n", 'Type' => 'public', 'JobStatus' => null, 'Jobs' => array() ); $actual = $changes->first()->get(); unset($actual['Date'], $actual['Files']); $this->assertEquals( $expected, $actual, 'Fetched change should match expected values.' ); // battery of tests against this setup with various options $tests = array( array( 'label' => __LINE__ .': defaults', 'options' => array(), 'expected' => array( "Integration\n", "in alternate client\n", "Has 2 files\n", "test-2", "test-1" ), ), array( 'label' => __LINE__ .': fetch maximum 1', 'options' => array( Change::FETCH_MAXIMUM => 1, ), 'expected' => array( "Integration\n", ), ), array( 'label' => __LINE__ .': fetch maximum 2', 'options' => array( Change::FETCH_MAXIMUM => 2, ), 'expected' => array( "Integration\n", "in alternate client\n", ), ), array( 'label' => __LINE__ .': fetch maximum 3', 'options' => array( Change::FETCH_MAXIMUM => 3, ), 'expected' => array( "Integration\n", "in alternate client\n", "Has 2 files\n", ), ), array( 'label' => __LINE__ .': fetch maximum 4', 'options' => array( Change::FETCH_MAXIMUM => 4, ), 'expected' => array( "Integration\n", "in alternate client\n", "Has 2 files\n", "test-2", ), ), array( 'label' => __LINE__ .': fetch maximum 5', 'options' => array( Change::FETCH_MAXIMUM => 5, ), 'expected' => array( "Integration\n", "in alternate client\n", "Has 2 files\n", "test-2", "test-1" ), ), array( 'label' => __LINE__ .': fetch maximum 6', 'options' => array( Change::FETCH_MAXIMUM => 6, ), 'expected' => array( "Integration\n", "in alternate client\n", "Has 2 files\n", "test-2", "test-1" ), ), array( 'label' => __LINE__ .': fetch maximum 0', 'options' => array( Change::FETCH_MAXIMUM => 0, ), 'expected' => array( "Integration\n", "in alternate client\n", "Has 2 files\n", "test-2", "test-1" ), ), array( 'label' => __LINE__ .': fetch by filespec //depot/.../test-file', 'options' => array( Change::FETCH_BY_FILESPEC => '//depot/.../test-file', ), 'expected' => array( "test-2", "test-1" ), ), array( 'label' => __LINE__ .': fetch by status submitted', 'options' => array( Change::FETCH_BY_STATUS => 'submitted', ), 'expected' => array( "Integration\n", "test-2", "test-1" ), ), array( 'label' => __LINE__ .': fetch by status pending', 'options' => array( Change::FETCH_BY_STATUS => 'pending', ), 'expected' => array( "in alternate client\n", "Has 2 files\n", ), ), array( 'label' => __LINE__ .': fetch by client regular', 'options' => array( Change::FETCH_BY_CLIENT => $this->p4->getClient(), ), 'expected' => array( "Has 2 files\n", "test-2", "test-1" ), ), array( 'label' => __LINE__ .': fetch by client alternate', 'options' => array( Change::FETCH_BY_CLIENT => $clientId, ), 'expected' => array( "Integration\n", "in alternate client\n", ), ), array( 'label' => __LINE__ .': fetch by user regular', 'options' => array( Change::FETCH_BY_USER => $this->p4->getUser(), ), 'expected' => array( "Has 2 files\n", "test-2", "test-1" ), ), array( 'label' => __LINE__ .': fetch by user alternate', 'options' => array( Change::FETCH_BY_USER => $user->getId(), ), 'expected' => array( "Integration\n", "in alternate client\n", ), ), array( 'label' => __LINE__ .': fetch without integrated', 'options' => array( Change::FETCH_INTEGRATED => false, Change::FETCH_BY_FILESPEC => $integFilespec, ), 'expected' => array( "Integration\n", ), ), array( 'label' => __LINE__ .': fetch with integrated', 'options' => array( Change::FETCH_INTEGRATED => true, Change::FETCH_BY_FILESPEC => $integFilespec, ), 'expected' => array( "Integration\n", "test-1" ), ), array( 'label' => __LINE__ .': fetch with after', 'options' => array( Change::FETCH_AFTER => 4 ), 'expected' => array( "Has 2 files\n", 'test-2', 'test-1' ), ), ); foreach ($tests as $test) { $label = $test['label']; $changes = Change::fetchAll($test['options']); $this->assertEquals( count($test['expected']), count($changes), "$label - Expected change count." ); $descriptions = array(); foreach ($changes as $change) { $descriptions[] = $change->getDescription(); } $this->assertSame( $test['expected'], $descriptions, "$label - Expected change descriptions." ); } } /** * Test getJobs and setJobs. */ public function testAddGetSetJobs() { // test initial state of jobs in a fresh change object. $change = new Change; $jobs = $change->getJobs(); $this->assertTrue(is_array($jobs), 'Expect an array from getJobs.'); $this->assertSame(0, count($jobs), 'There should be no jobs associated with a fresh change.'); // create a job $job = new Job; $job->set('Description', 'This is job #1'); $job->save(); // create a change to associate with the job $change = new Change; $change->setJobs(array($job->getId())); $jobs = $change->getJobs(); $this->assertSame(1, count($jobs), 'Expect one job.'); $this->assertSame($job->getId(), $jobs[0], 'Expect matching job id.'); // add another job $extraJobId = 'anotherJob'; $change->addJob($extraJobId); $jobs = $change->getJobs(); $this->assertSame(2, count($jobs), 'Expect two jobs.'); $this->assertSame($job->getId(), $jobs[0], 'Expect matching job id.'); $this->assertSame($extraJobId, $jobs[1], 'Expect matching job id for added job.'); // attempt to save the change with a non-existant job try { $change->setDescription('A change with jobs.') ->save('save the change'); $this->fail('Unexpected success saving change with a non-existant job.'); } catch (CommandException $e) { $this->assertRegexp( "/Job 'anotherJob' doesn't exist\./", $e->getMessage(), 'Expected error saving change with a non-existant job.' ); } catch (\Exception $e) { $this->fail( 'Unexpected exception saving change with a non-existant job (' . get_class($e) .') '. $e->getMessage() ); } // create the non-existant job. $job2 = new Job; $job2->set('Description', 'Was non-existant') ->setId($extraJobId) ->save(); // re-attempt saving the change now that the job exists. $change->setDescription('Change with jobs.') ->save(); // fetch the changes we have, and verify the jobs. $changes = Change::fetchAll(); $this->assertEquals(1, count($changes), 'Expect one change.'); $this->assertSame(2, count($changes->first()->getJobs()), 'Expect 2 jobs with fetched change.'); $this->assertSame( array($extraJobId, $job->getId()), $changes->first()->getJobs(), 'Expected jobs in fetched change.' ); $this->assertTrue($changes->first()->isPending(), 'Change should be pending.'); $this->assertFalse($changes->first()->isSubmitted(), 'Change should not be submitted.'); // now submit the change, and check the jobs. $file = new File; $file->setFilespec('//depot/file.txt')->add()->setLocalContents('File content.'); $change->addFile($file); $change->submit(); $changes = Change::fetchAll(); $this->assertEquals(1, count($changes), 'Expect one change.'); $this->assertSame(2, count($changes->first()->getJobs()), 'Expect 2 jobs with fetched & submitted change.'); $this->assertSame( array($extraJobId, $job->getId()), $changes->first()->getJobs(), 'Expected jobs in fetched & submitted change.' ); $this->assertFalse($changes->first()->isPending(), 'Change should not be pending.'); $this->assertTrue($changes->first()->isSubmitted(), 'Change should be submitted.'); // create a new change, with non-existant jobId, and submit it. $change = new Change; $change->setDescription('Try submitting with non-existant job.'); $thirdJobId = 'yetAnotherJob'; $change->addJob($thirdJobId); $file = new File; $file->setFileSpec('//depot/file2.txt')->add()->setLocalContents('File content.'); $change->addFile($file); $this->assertSame(null, $change->getId(), 'Expect id prior to submit.'); try { $change->submit('Attempt 1.'); $this->fail('Unexpected success submitting change with a non-existant job.'); } catch (CommandException $e) { $this->assertRegexp( "/Change 2 created.*Job '$thirdJobId' doesn't exist\./s", $e->getMessage(), 'Expected error message submitting change with a non-existant job.' ); } catch (\Exception $e) { $this->fail( 'Unexpected exception submitting change with a non-existant job (' . get_class($e) .') '. $e->getMessage() ); } $this->assertSame(2, $change->getId(), 'Expected id after submit.'); // create the job $job3 = new Job; $job3->set('Description', 'Was non-existant') ->setId($thirdJobId) ->save(); // submit should now succeed. $change->submit('Attempt 2.'); $changes = Change::fetchAll(); $this->assertEquals(2, count($changes), 'Expect two changes.'); $this->assertEquals(1, count($changes->first()->getJobs()), 'Expect 1 job with second submitted change.'); $this->assertSame( array($thirdJobId), $changes->first()->getJobs(), 'Expected jobs in second submitted change.' ); } /** * Test functionality of the setFixStatus() method. */ public function testFixStatus() { // modify job spec to add custom statuses $spec = Definition::fetch('job'); $fields = $spec->getFields(); $fields['Status']['options'] = array('a', 'b', 'c', 'd', 'closed'); $fields['Status']['default'] = 'a'; $spec->setFields($fields)->save(); // open a file for add $file = new File; $file->setFilespec("//depot/foo")->setLocalContents("this is a test")->add(); // create couple of jobs $job1 = new Job; $job1->set('Description', 'job 1') ->save(); $job2 = new Job; $job2->set('Description', 'job 2') ->setStatus('b') ->save(); // save a change with fixStatus set to 'same' $change = new Change; $change->setDescription("change test 1.\n") ->addFile($file) ->setJobs(array($job1->getId(), $job2->getId())) ->setFixStatus('same') ->save(); // verify job statuses before submit $this->assertSame('a', $job1->getStatus()); $this->assertSame('b', $job2->getStatus()); // submit a change and verify job statuses after $change->submit(); $this->assertSame('a', Job::fetch($job1->getId())->getStatus()); $this->assertSame('b', Job::fetch($job2->getId())->getStatus()); // try again with a new change and fixStatus set to 'd' $file = new File; $file->setFilespec("//depot/bar")->setLocalContents("this is a test")->add(); $change = new Change; $change->setDescription("change test 2.\n") ->addFile($file) ->setJobs(array($job1->getId(), $job2->getId())) ->setFixStatus('d') ->save() ->submit(); // verify job statuses after submit $this->assertSame('d', Job::fetch($job1->getId())->getStatus()); $this->assertSame('d', Job::fetch($job2->getId())->getStatus()); // try again without using fixStatus $file = new File; $file->setFilespec("//depot/baz")->setLocalContents("this is a test")->add(); $change = new Change; $change->setDescription("change test 3.\n") ->addFile($file) ->setJobs(array($job1->getId(), $job2->getId())) ->save() ->submit(); // verify job statuses after submit $this->assertSame('closed', Job::fetch($job1->getId())->getStatus()); $this->assertSame('closed', Job::fetch($job2->getId())->getStatus()); } /** * Test getFiles and setFiles. */ public function testGetSetFiles() { // test initial state of files in a fresh change object. $change = new Change; $files = $change->getFiles(); $this->assertTrue(is_array($files), 'Expect an array from getFiles.'); $this->assertSame(0, count($files), 'There should be no files associated with a fresh change.'); // create two submitted changes, and one pending $file1 = new File; $filespec1 = '//depot/change1.txt'; $file1->setFilespec($filespec1)->add()->setLocalContents('content1')->submit('File 1'); $file2 = new File; $filespec2 = '//depot/change2.txt'; $file2->setFilespec($filespec2)->add()->setLocalContents('content2')->submit('File 2'); $file3 = new File; $filespec3 = '//depot/change3.txt'; $file3->setFilespec($filespec3)->add()->setLocalContents('content3'); $this->assertTrue($file3->isOpened(), 'File #3 should be opened.'); // test that we get the appropriate files back for each change $change = Change::fetch(1); $this->assertFalse($change->isPending(), 'Change 1 should not be pending.'); $files = $change->getFiles(); $this->assertSame(1, count($files), 'There should be one file associated with change 1.'); $this->assertSame($filespec1.'#1', $files[0], 'Expected filespec for change 1.'); $change = Change::fetch(2); $this->assertFalse($change->isPending(), 'Change 2 should not be pending.'); $files = $change->getFiles(); $this->assertSame(1, count($files), 'There should be one file associated with change 2.'); $this->assertSame($filespec2.'#1', $files[0], 'Expected filespec for change 2.'); $change = Change::fetch('default'); $this->assertTrue($change->isPending(), 'Change 3 should be pending.'); $files = $change->getFiles(); $this->assertSame(1, count($files), 'There should be one file associated with change 3.'); $this->assertSame($filespec3, $files[0], 'Expected filespec for change 3.'); // test that setting a comment on a pending changelist does not influence // getFiles handling. $change->setDescription('This is the default change.'); $files = $change->getFiles(); $this->assertSame(1, count($files), 'There should be one file associated with change 3.'); $this->assertSame($filespec3, $files[0], 'Expected filespec for change 3.'); // test that we cannot setFiles on a submitted changelist. try { $change = Change::fetch(2); $change->setFiles(array($filespec3)); $this->fail('Unexpected success setting files on a submitted changelist.'); } catch (SpecException $e) { $this->assertSame( 'Cannot set files on a submitted change.', $e->getMessage(), 'Expected error setting files on a submitted changelist' ); } catch (\Exception $e) { $this->fail( 'Unexpected exception setting files on a submitted changelist: (' . get_class($e) .') '. $e->getMessage() ); } // test that we can setFiles on a pending changelist, and that getFiles // returns the same list. $change = new Change; $change->setId('default'); $change->setFiles(array($filespec1)); $files = $change->getFiles(); $this->assertSame(1, count($files), 'There should be one file associated with change 3 after setFiles.'); $this->assertSame($filespec1, $files[0], 'Expected filespec for change 3 after setFiles.'); // test that we can set the files to null to empty the list, and that we get // empty list in return. $change->setFiles(null); $files = $change->getFiles(); $this->assertSame(0, count($files), 'There should now be no files associated with change 3.'); // test that fetching a new change object returns the original files. $change = Change::fetch('default'); $files = $change->getFiles(); $this->assertSame(1, count($files), 'There should be one file associated with change 3.'); $this->assertSame($filespec3, $files[0], 'Expected filespec for change 3.'); // test that we can setFiles with an iterator of P4\File objects. $files = new FieldedIterator; $files[] = $file1; $files[] = $file2; $files[] = $file3; $change->setFiles($files); $files = $change->getFiles(); $this->assertSame(3, count($files), 'There should now be three files associated with change 3.'); $this->assertSame($filespec1, $files[0], 'Expected filespec for change 3, file 0.'); $this->assertSame($filespec2, $files[1], 'Expected filespec for change 3, file 1.'); $this->assertSame($filespec3, $files[2], 'Expected filespec for change 3, file 2.'); } /** * Test accessors. */ public function testAccessors() { // save a basic change. $change = new Change; $description = "this is a test\n"; $change->setDescription($description); $change->save(); // open a file for add. $file = new File; $file->setFilespec('//depot/test-file') ->add(); // create a job. $job = new Job; $job->set('Description', 'fix something') ->save(); // save a change with a file and a job. $change2 = new Change; $description2 = "a change with a file and a job.\n"; $change2->setDescription($description2) ->setFiles(array($file->getFilespec())) ->setJobs(array($job->getId())) ->save(); // ensure fetched change contains expected data. $tests = array( // test accessors for a brand new (unpopulated) change object. array( 'label' => __LINE__ . ': New change object', 'change' => new Change, 'method' => 'getId', 'expected' => null ), array( 'label' => __LINE__ . ': New change object', 'change' => new Change, 'method' => 'getDate', 'expected' => null ), array( 'label' => __LINE__ . ': New change object', 'change' => new Change, 'method' => 'getUser', 'expected' => $this->p4->getUser() ), array( 'label' => __LINE__ . ': New change object', 'change' => new Change, 'method' => 'getClient', 'expected' => $this->p4->getClient() ), array( 'label' => __LINE__ . ': New change object', 'change' => new Change, 'method' => 'getStatus', 'expected' => 'pending' ), array( 'label' => __LINE__ . ': New change object', 'change' => new Change, 'method' => 'getDescription', 'expected' => null ), array( 'label' => __LINE__ . ': New change object', 'change' => new Change, 'method' => 'getJobStatus', 'expected' => null ), array( 'label' => __LINE__ . ': New change object', 'change' => new Change, 'method' => 'getJobs', 'expected' => array() ), // test accessors for a saved change object. array( 'label' => __LINE__ . ': Saved change object', 'change' => $change, 'method' => 'getId', 'expected' => 1 ), // datetime not tested here - see later test array( 'label' => __LINE__ . ': Saved change object', 'change' => $change, 'method' => 'getUser', 'expected' => $this->p4->getUser() ), array( 'label' => __LINE__ . ': Saved change object', 'change' => $change, 'method' => 'getClient', 'expected' => $this->p4->getClient() ), array( 'label' => __LINE__ . ': Saved change object', 'change' => $change, 'method' => 'getStatus', 'expected' => 'pending' ), array( 'label' => __LINE__ . ': Saved change object', 'change' => $change, 'method' => 'getDescription', 'expected' => $description ), array( 'label' => __LINE__ . ': Saved change object', 'change' => $change, 'method' => 'getJobStatus', 'expected' => null ), array( 'label' => __LINE__ . ': Saved change object', 'change' => $change, 'method' => 'getJobs', 'expected' => array() ), // test accessors for a change object with a file and a job attached. array( 'label' => __LINE__ . ': Change w. a file and a job', 'change' => $change2, 'method' => 'getId', 'expected' => 2 ), // datetime not tested here - see later test array( 'label' => __LINE__ . ': Change w. a file and a job', 'change' => $change2, 'method' => 'getUser', 'expected' => $this->p4->getUser() ), array( 'label' => __LINE__ . ': Change w. a file and a job', 'change' => $change2, 'method' => 'getClient', 'expected' => $this->p4->getClient() ), array( 'label' => __LINE__ . ': Change w. a file and a job', 'change' => $change2, 'method' => 'getStatus', 'expected' => 'pending' ), array( 'label' => __LINE__ . ': Change w. a file and a job', 'change' => $change2, 'method' => 'getDescription', 'expected' => $description2 ), array( 'label' => __LINE__ . ': Change w. a file and a job', 'change' => $change2, 'method' => 'getJobStatus', 'expected' => null ), array( 'label' => __LINE__ . ': Change w. a file and a job', 'change' => $change2, 'method' => 'getJobs', 'expected' => array($job->getId()) ), ); // run each test. foreach ($tests as $test) { $label = $test['label'] .' - '. $test['method']; $this->assertSame( $test['expected'], $test['change']->{$test['method']}(), "$label - expected value." ); } } /** * test setDescription. */ public function testSetDescription() { $tests = array( array( 'label' => __LINE__ .': null', 'description' => null, 'error' => false, ), array( 'label' => __LINE__ .': empty string', 'description' => '', 'error' => false, ), array( 'label' => __LINE__ .': array', 'description' => array(), 'error' => true, ), array( 'label' => __LINE__ .': integer', 'description' => 123, 'error' => true, ), array( 'label' => __LINE__ .': float', 'description' => -1.23, 'error' => true, ), array( 'label' => __LINE__ .': string', 'description' => "have a nice day\n", 'error' => false, ), ); foreach ($tests as $test) { $change = new Change; $label = $test['label']; try { $change->setDescription($test['description']); if ($test['error']) { $this->fail("$label - Unexpected success."); } } catch (\InvalidArgumentException $e) { if ($test['error']) { $this->assertSame( 'Cannot set description. Invalid type given.', $e->getMessage(), "$label - Expected error." ); } else { $this->fail("$label - Unexpected argument exception: ". $e->getMessage()); } } catch (\Exception $e) { $this->fail("$label - Unexpected exception: (". get_class($e) .') '. $e->getMessage()); } if (!$test['error']) { $this->assertSame( $test['description'], $change->getDescription(), "$label - Expect to get same description as set." ); } } } /** * test setFiles. * * @todo add a test for setting files on a submitted change when submits work. */ public function testSetFiles() { // create an iterator with files $files = new FieldedIterator; $file = new File; $files[] = $file->setFilespec('//depot/file1.txt'); $file = new File; $files[] = $file->setFilespec('//depot/file2.txt'); $file = new File; $files[] = $file->setFilespec('//depot/file3.txt'); $filesIteratorInvalid = new FieldedIterator; $filesIteratorInvalid[] = $file; $filesIteratorInvalid[] = new Client; $tests = array( array( 'label' => __LINE__ .': iterator', 'files' => $files, 'error' => false, 'expected' => array( $files[0]->getFilespec(), $files[1]->getFilespec(), $files[2]->getFilespec(), ), ), array( 'label' => __LINE__ .': invalid iterator', 'files' => $filesIteratorInvalid, 'error' => new \InvalidArgumentException('All files must be a string or P4\File'), 'expected' => false, ), array( 'label' => __LINE__ .': null', 'files' => null, 'error' => false, 'expected' => array(), ), array( 'label' => __LINE__ .': empty string', 'files' => '', 'error' => new \InvalidArgumentException('Cannot set files. Invalid type given.'), 'expected' => false, ), array( 'label' => __LINE__ .': integer', 'files' => 123, 'error' => new \InvalidArgumentException('Cannot set files. Invalid type given.'), 'expected' => false, ), array( 'label' => __LINE__ .': float', 'files' => -1.23, 'error' => new \InvalidArgumentException('Cannot set files. Invalid type given.'), 'expected' => false, ), array( 'label' => __LINE__ .': string', 'files' => "have a nice day\n", 'error' => new \InvalidArgumentException('Cannot set files. Invalid type given.'), 'expected' => false, ), array( 'label' => __LINE__ .': empty array', 'files' => array(), 'error' => false, 'expected' => array(), ), array( 'label' => __LINE__ .': array with numerics', 'files' => array(1, 2), 'error' => new \InvalidArgumentException('All files must be a string or P4\File'), 'expected' => array(), ), array( 'label' => __LINE__ .': array with strings and numerics', 'files' => array('one', 2), 'error' => new \InvalidArgumentException('All files must be a string or P4\File'), 'expected' => array(), ), array( 'label' => __LINE__ .': array with a string', 'files' => array('one'), 'error' => false, 'expected' => array('one'), ), array( 'label' => __LINE__ .': array with multiple strings', 'files' => array('one', 'two', 'three'), 'error' => false, 'expected' => array('one', 'two', 'three'), ), ); foreach ($tests as $test) { $change = new Change; $label = $test['label']; try { $change->setFiles($test['files']); if ($test['error']) { $this->fail("$label - Unexpected success."); } } catch (\Exception $e) { if ($test['error']) { $this->assertSame( $test['error']->getMessage(), $e->getMessage(), "$label - Expected error." ); $this->assertSame( get_class($test['error']), get_class($e), "$label - Expected error class." ); } else { $this->fail("$label - Unexpected exception: (". get_class($e) .') '. $e->getMessage()); } } if (!$test['error']) { $this->assertTrue( is_array($change->getFiles()), "$label - Change getFiles should return array." ); $this->assertSame( count($test['expected']), count($change->getFiles()), "$label - Change should contain same number of files." ); } } } /** * test setJobs. * * @todo add a test for setting jobs on a submitted change when submits work. */ public function testSetJobs() { // create an iterator with jobs. $jobs = new FieldedIterator; $job = new Job; $jobs[] = $job->setId('job000001'); $job = new Job; $jobs[] = $job->setId('job000002'); $job = new Job; $jobs[] = $job->setId('job000003'); $jobsIteratorInvalid = new FieldedIterator; $jobsIteratorInvalid[] = $job; $jobsIteratorInvalid[] = new Client; // define inputs to set jobs. $tests = array( array( 'label' => __LINE__ .': iterator', 'jobs' => $jobs, 'error' => false, 'expected' => array('job000001', 'job000002', 'job000003'), ), array( 'label' => __LINE__ .': iterator with invalid element', 'jobs' => $jobsIteratorInvalid, 'error' => new \InvalidArgumentException('Each job must be a string.'), 'expected' => false, ), array( 'label' => __LINE__ .': null', 'jobs' => null, 'error' => false, 'expected' => array(), ), array( 'label' => __LINE__ .': empty string', 'jobs' => '', 'error' => new \InvalidArgumentException('Cannot set jobs. Invalid type given.'), 'expected' => false, ), array( 'label' => __LINE__ .': integer', 'jobs' => 123, 'error' => new \InvalidArgumentException('Cannot set jobs. Invalid type given.'), 'expected' => false, ), array( 'label' => __LINE__ .': float', 'jobs' => -1.23, 'error' => new \InvalidArgumentException('Cannot set jobs. Invalid type given.'), 'expected' => false, ), array( 'label' => __LINE__ .': string', 'jobs' => "have a nice day\n", 'error' => new \InvalidArgumentException('Cannot set jobs. Invalid type given.'), 'expected' => false, ), array( 'label' => __LINE__ .': empty array', 'jobs' => array(), 'error' => false, 'expected' => array(), ), array( 'label' => __LINE__ .': array with numerics', 'jobs' => array(1, 2), 'error' => new \InvalidArgumentException('Each job must be a string.'), 'expected' => array(), ), array( 'label' => __LINE__ .': array with strings and numerics', 'jobs' => array('one', 2), 'error' => new \InvalidArgumentException('Each job must be a string.'), 'expected' => array(), ), array( 'label' => __LINE__ .': array with a string', 'jobs' => array('one'), 'error' => false, 'expected' => array('one'), ), array( 'label' => __LINE__ .': array with multiple strings', 'jobs' => array('one', 'two', 'three'), 'error' => false, 'expected' => array('one', 'two', 'three'), ), ); foreach ($tests as $test) { $change = new Change; $label = $test['label']; try { $change->setJobs($test['jobs']); if ($test['error']) { $this->fail("$label - Unexpected success."); } } catch (\Exception $e) { if ($test['error']) { $this->assertSame( $test['error']->getMessage(), $e->getMessage(), "$label - Expected error." ); $this->assertSame( get_class($test['error']), get_class($e), "$label - Expected error class." ); } else { $this->fail("$label - Unexpected exception: (". get_class($e) .') '. $e->getMessage()); } } if (!$test['error']) { $this->assertSame( $test['expected'], $change->getJobs(), "$label - Expected jobs." ); } } } /** * Test moving files between changelists. */ public function testReopen() { // open a file for add. $file = new File; $file->setFilespec("//depot/test-file") ->add(); // put the file in a change. $change1 = new Change; $change1->setDescription("test 1") ->addFile("//depot/test-file") ->save(); $this->assertTrue( count($change1->getFiles()) == 1, "Change should have one file." ); $this->assertTrue( in_array("//depot/test-file", $change1->getFiles()), "test-file should be in change." ); // try to put the same file in a different change. $change2 = new Change; $change2->setDescription("test 2") ->addFile("//depot/test-file") ->save(); $this->assertTrue( count($change2->getFiles()) == 1, "Change should have one file." ); $this->assertTrue( in_array("//depot/test-file", $change2->getFiles()), "test-file should be in change." ); // try to put same file back in first (now numbered) change. $change1->addFile("//depot/test-file")->save(); $this->assertTrue( count($change1->getFiles()) == 1, "Change should have one file." ); $this->assertTrue( in_array("//depot/test-file", $change1->getFiles()), "test-file should be in change." ); // attempting to put a un-opened file in a change should fail. $change3 = new Change; try { $change3->setDescription("test 3")->addFile("//depot/foo")->save(); $this->fail("Save change with unopened file should throw exception."); } catch (UnopenedException $e) { $this->assertTrue(true); } } /** * Test deleting changes. */ public function testDelete() { // delete an unidentified change. $change = new Change; try { $change->delete(); $this->fail("Delete change without id should fail."); } catch (SpecException $e) { $this->assertTrue(true); } // delete the default change. $change = new Change; $change->setId('default'); try { $change->delete(); $this->fail("Delete default change should fail."); } catch (SpecException $e) { $this->assertTrue(true); } // delete a non-existant change. $change = new Change; $change->setId('123'); try { $change->delete(); $this->fail("Delete non-existent change should fail."); } catch (NotFoundException $e) { $this->assertTrue(true); } // delete a real pending change w. no files. $change = new Change; $change->setDescription("test")->save()->delete(); $this->assertFalse( Change::exists($change->getId()), "Deleted change should no longer exist." ); // delete a real pending change w. files. $file = new File; $file->setFilespec("//depot/test-file")->add(); $change = new Change; $change->setDescription("test")->addFile("//depot/test-file")->save()->delete(); $this->assertFalse( Change::exists($change->getId()), "Deleted change should no longer exist." ); // delete a real pending change w. jobs attached. $job = new Job; $job->set("Description", "test-job")->save(); $change = new Change; $change->setDescription("test")->addJob($job->getId())->save()->delete(); $this->assertFalse( Change::exists($change->getId()), "Deleted change should no longer exist." ); // create a change under another client workspace. $client = new Client; $client->setId("another-test-client") ->setRoot(DATA_PATH . "/clients/another-test-client") ->save(); $p4 = Connection::factory( $this->p4->getPort(), $this->p4->getUser(), $client->getId(), $this->getP4Params('password') ); $change = new Change($p4); $change->setDescription("test-change")->save(); $id = $change->getId(); // delete another client's change. $change = Change::fetch($id); try { $change->delete(); $this->fail("Delete of another client change should fail."); } catch (SpecException $e) { $this->assertTrue(true); } // delete again, but with force option. $change->delete(true); $this->assertFalse( Change::exists($change->getId()), "Deleted change should no longer exist." ); // test delete of a submitted change. $file = new File; $file->setFilespec("//depot/foo") ->setLocalContents("this is a test") ->add() ->submit("test"); $change = Change::fetch($file->getStatus('headChange')); try { $change->delete(); $this->fail("Delete of submitted change should fail."); } catch (SpecException $e) { $this->assertSame( "Cannot delete a submitted change without the force option.", $e->getMessage(), "Unexpected exception message." ); } try { $change->delete(true); $this->fail("Delete of submitted change should fail."); } catch (SpecException $e) { $this->assertSame( "Cannot delete a submitted change that contains files.", $e->getMessage(), "Unexpected exception message." ); } // obliterate the files in change. $this->p4->run("obliterate", array("-y", "//...@=" . $change->getId())); $change = Change::fetch($change->getId()); $change->delete(true); $this->assertFalse( Change::exists($change->getId()), "Change should no longer exist." ); } /** * Test submitting of changes. */ public function testSubmit() { $file = new File; $file->setFilespec("//depot/foo")->setLocalContents("this is a test")->add(); // ensure no changes to start. $changes = Change::fetchAll(); $this->assertSame( 0, count($changes), "There should be no changes." ); // do a submit. $change = new Change; $change->addFile($file)->submit("test submit"); // ensure change was successful. $changes = Change::fetchAll(); $this->assertSame( 1, count($changes), "There should be one change." ); $this->assertSame( Change::SUBMITTED_CHANGE, $change->getStatus(), "Change should be submitted." ); // test that saving a submitted change results in exception try { $change->save(); $this->fail('Unexpected success saving a submitted change.'); } catch (SpecException $e) { $this->assertSame( 'Cannot update a submitted change without the force option.', $e->getMessage(), 'Expected exception saving a submitted change.' ); } catch (\Exception $e) { $this->fail( 'Unexpected exception saving a submitted change (' . get_class($e) .') '. $e->getMessage() ); } // test that submitted a submitted change results in exception try { $change->submit(); $this->fail('Unexpected success submitting a submitted change.'); } catch (SpecException $e) { $this->assertSame( 'Can only submit pending changes.', $e->getMessage(), 'Expected exception submitting a submitted change.' ); } catch (\Exception $e) { $this->fail( 'Unexpected exception submitting a submitted change (' . get_class($e) .') '. $e->getMessage() ); } // test that setting jobs on a submitted change results in exception try { $change->setJobs(array()); $this->fail('Unexpected success setting jobs on a submitted change.'); } catch (SpecException $e) { $this->assertSame( 'Cannot set jobs on a submitted change.', $e->getMessage(), 'Expected exception setting jobs on a submitted change.' ); } catch (\Exception $e) { $this->fail( 'Unexpected exception setting jobs on a submitted change (' . get_class($e) .') '. $e->getMessage() ); } } /** * Verify submit works if change is renumbered (was broken at one point). */ public function testSubmitRenumber() { $file = new File; $file->setFilespec("//depot/foo")->setLocalContents("this is a test")->add(); // make the change. $change = new Change; $change->addFile($file)->save(); // make another change to induce renumbering $change2 = new Change; $change2->setDescription('bump change number')->save(); $change->submit("test submit"); // ensure submit was successful. $changes = Change::fetchAll(); $this->assertSame( 2, count($changes), "There should be two changes." ); $this->assertSame( Change::SUBMITTED_CHANGE, $change->getStatus(), "Change should be submitted." ); } /** * Test submit resolve behavior. */ public function testSubmitConflicts() { // create a second client. $client = new Client; $client->setId("client-2") ->setRoot($this->getP4Params('clientRoot') . '/client-2') ->addView("//depot/...", "//client-2/...") ->save(); // connect w. second client. $p4 = Connection::factory( $this->p4->getPort(), $this->p4->getUser(), $client->getId(), $this->getP4Params('password') ); // create a situation where resolve is needed. // a. from the main test client add/submit 'foo', then edit it. // b. from another client sync/edit/submit 'foo'. $file1 = new File; $file1->setFilespec("//depot/foo") ->setLocalContents("contents-1") ->add() ->submit("change 1") ->edit(); $file2 = new File($p4); $file2->setFilespec("//depot/foo") ->sync() ->edit() ->submit("change 2"); // try to submit a change w. files needing resolve. $change = new Change; try { $change->addFile($file1) ->submit("main client submit"); $this->fail("Unexpected success; submit should fail."); } catch (ConflictException $e) { $files = $change->getFilesToResolve(); $this->assertEquals( 1, count($files), "Expected one file needing resolve for submit." ); $this->assertSame( $file1->getFilespec(), $files[0]->getFilespec(), "Expected matching filespecs." ); } // create a situation where revert is needed. // a. from the main test client add 'foo'. // b. from another client add/submit 'foo'. $file1 = new File; $file1->setFilespec("//depot/bar") ->setLocalContents("contents-1") ->add(); $file2 = new File($p4); $file2->setFilespec("//depot/bar") ->setLocalContents("contents-1") ->add() ->submit("change 2") ->edit() ->submit("change 3"); // try to submit a change w. files needing revert. $change = new Change; try { $change->addFile($file1) ->submit("main client submit"); $this->fail("Unexpected success; submit should fail."); } catch (ConflictException $e) { $files = $change->getFilesToRevert(); $this->assertEquals( 1, count($files), "Expected one file needing resolve for revert." ); $this->assertSame( $file1->getFilespec(), $files[0]->getFilespec(), "Expected matching filespecs." ); } // create another situation where revert is needed. // a. from the main test client add/submit 'foo', then edit it. // b. from another client sync/delete/submit 'foo'. $file1 = new File; $file1->setFilespec("//depot/baz") ->setLocalContents("contents-1") ->add() ->submit("change 1") ->edit(); $file2 = new File($p4); $file2->setFilespec("//depot/baz") ->sync() ->delete() ->submit("change 2"); // try to submit a change w. files needing revert. $change = new Change; try { $change->addFile($file1) ->submit("main client submit"); $this->fail("Unexpected success; submit should fail."); } catch (ConflictException $e) { $files = $change->getFilesToRevert(); $this->assertSame( 1, count($files), "Expected one file needing resolve." ); $this->assertSame( $file1->getFilespec(), $files[0]->getFilespec(), "Expected matching filespecs." ); } } /** * Test submit resolve behavior when passing resolve options. */ public function testSubmitResolveConflicts() { // create a second client. $client = new Client; $client->setId("client-2") ->setRoot($this->getP4Params('clientRoot') . '/client-2') ->addView("//depot/...", "//client-2/...") ->save(); // connect w. second client. $p4 = Connection::factory( $this->p4->getPort(), $this->p4->getUser(), $client->getId(), $this->getP4Params('password') ); // create a situation where resolve is needed. // a. from the main test client add/submit 'foo', then edit it. // b. from another client sync/edit/submit 'foo'. $file1 = new File; $file1->setFilespec("//depot/foo") ->setLocalContents("contents-1") ->add() ->submit("change 1") ->edit(); $file2 = new File($p4); $file2->setFilespec("//depot/foo") ->sync() ->edit() ->submit("change 2"); // try to submit a change w. files needing resolve. $change = new Change; $change->addFile($file1) ->submit("main client submit", Change::RESOLVE_ACCEPT_YOURS); } /** * test bad change numbers against P4\Validate\ChangeNumber */ public function testBadValidateChangeNumber() { $tests = array ( array( 'label' => __LINE__ .': null', 'value' => null, ), array( 'label' => __LINE__ .': empty', 'value' => '', ), array( 'label' => __LINE__ .': negative', 'value' => -1, ), array( 'label' => __LINE__ .': float', 'value' => 10.10, ), ); foreach ($tests as $test) { $label = $test['label']; $validator = new \P4\Validate\ChangeNumber(); $this->assertSame( false, $validator->isValid($test['value']), "$label - Expected Invalid" ); } } /** * Test a fetch/save use case that failed in the past. */ public function testSave() { $change = Change::fetch('default'); $change->setDescription('Test submit') ->save(); } /** * Test a save but with no description */ public function testSaveNoDescription() { $change = Change::fetch('default'); $change->save(); } /** * Test updating a submitted change description. */ public function testUpdateSubmitted() { $file = new File; $file->setFilespec("//depot/foo") ->setLocalContents("this is a test") ->add() ->submit('original description'); $change = Change::fetch(1); $this->assertSame("original description\n", $change->getDescription()); $change->setDescription('updated description') ->save(true); $change = Change::fetch(1); $this->assertSame("updated description\n", $change->getDescription()); } /** * Test reverting an entire change. */ public function testRevert() { $file1 = new File; $file1->setFilespec('//depot/one'); $file1->add(); $file2 = new File; $file2->setFilespec('//depot/two'); $file2->add(); $file3 = new File; $file3->setFilespec('//depot/three'); $file3->add(); $change = new Change; $change->setDescription("Test change"); $change->setFiles(array('//depot/two', '//depot/three')); $change->save(); // check that we have three files open. $query = FileQuery::create() ->addFilespec('//depot/...') ->setLimitToOpened(true); $this->assertSame( 3, File::fetchAll($query)->count(), "Expected three open files" ); // revert the pending change. $change->revert(); // check that we have one file open. $this->assertSame( 1, File::fetchAll($query)->count(), "Expected one open file" ); // check that the correct files are opened. $file1->clearStatusCache(); $this->assertTrue($file1->isOpened()); $file2->clearStatusCache(); $this->assertFalse($file2->isOpened()); $file3->clearStatusCache(); $this->assertFalse($file3->isOpened()); } /** * Test getFileObjects and getFileObject */ public function testGetFileObjectsObject() { $file1 = new File; $file1->setFilespec('//depot/one') ->add() ->setLocalContents("contents-1"); $file2 = new File; $file2->setFilespec('//depot/two') ->add() ->setLocalContents("contents-2"); $change = new Change; $change->setDescription("Test change") ->setFiles(array($file1, $file2)) ->save(); $this->assertSame( $change->getFiles(), $change->getFileObjects()->invoke('getDepotFilename'), 'Expected get files to match fileobject list pre-submit' ); $change->submit('test'); File::fetch('//depot/one')->edit()->submit('rev two'); $this->assertSame( array('//depot/one#1', '//depot/two#1'), $change->getFiles(), 'Expected matching list of post-submit files' ); $this->assertSame( array('1', '1'), $change->getFileObjects()->invoke('getStatus', array('headRev')), 'Expected properly reved file objects post submit' ); $this->assertSame( '//depot/one', $change->getFileObject(File::fetch('//depot/one#2'))->getDepotFilename(), 'Expected getFileObject to provide proper depot filename' ); $this->assertSame( '//depot/two', $change->getFileObject(File::fetch('//depot/two'))->getDepotFilename(), 'Expected getFileObject to provide proper depot filename' ); $this->assertSame( '1', $change->getFileObject(File::fetch('//depot/one#2'))->getStatus('headRev'), 'Expected getFileObject to provide proper rev' ); try { $change->getFileObject('//depot/three'); $this->fail('Expected exception on getFileObject for invalid entry'); } catch (\InvalidArgumentException $e) { } try { $change->getFileObject(12); $this->fail('Expected exception on getFileObject for invalid type'); } catch (\InvalidArgumentException $e) { } } /** * Test getJobObjects and getJobObject */ public function testGetJobObjectsObject() { $job1 = new Job; $job1->setId('job1') ->setDescription('job1') ->save(); $job2 = new Job; $job2->setId('job2') ->setDescription('job2') ->save(); $change = new Change; $change->setDescription("Test change") ->setJobs(array($job1, $job2)) ->save(); $this->assertSame( $change->getJobs(), $change->getJobObjects()->invoke('getId'), 'Expected get jobs to match job object list pre-submit' ); $this->assertSame( 'job1', $change->getJobObject('job1')->getId(), 'Expected get job object to return result' ); // verify invalid id fails try { $change->getJobObject('job3'); } catch (\InvalidArgumentException $e) { } } /** * Test running fetch all with an array of ids */ public function testFetchAllByIds() { $file = new File; $file->setFilespec("//depot/file") ->add() ->setLocalContents("one") ->submit("test"); $file->edit() ->setLocalContents("two") ->submit("test2"); $file->edit() ->setLocalContents("three") ->submit("test3"); $changes = Change::fetchAll(array(Change::FETCH_BY_IDS => array('1', '3'))); $this->assertSame( array(1, 3), $changes->invoke('getId'), 'expected matching result' ); } /** * Test getting changes data */ public function testGetChangesData() { $change = new Change; try { $change->getChangesData(); $this->assertFalse(true, "Expected exception"); } catch (\P4\Spec\Exception\Exception $e) { $this->assertTrue(true); } $file = new File; $file->setFilespec("//depot/file") ->add() ->setLocalContents("one") ->submit("test"); $change = Change::fetch(1); $data = $change->getChangesData(); $this->assertSame('1', $data['change']); $this->assertSame('tester', $data['user']); $this->assertSame('//depot/*', $data['path']); // ensure it doesn't take any more commands to get the change path // and original id -- we verify this by peeking at the log $original = Logger::hasLogger() ? Logger::getLogger() : null; $logger = new ZendLogger; $mock = new MockLog; $logger->addWriter($mock); Logger::setLogger($logger); $this->assertSame('//depot', $change->getPath()); $this->assertSame(1, $change->getOriginalId()); $this->assertSame(0, count($mock->events)); // restore original logger if there is one. Logger::setLogger($original); } /** * Test setting a new client on an existing change */ public function testSetClient() { $change = new Change($this->p4); $change->setDescription('test')->save(); $oldClient = $change->getClient(); $this->p4->getService('clients')->grab(); $change->setClient($this->p4->getClient()); $change->save(); $this->p4->getService('clients')->release(); $this->assertFalse( $oldClient == Change::fetch($change->getId(), $this->p4)->getClient(), 'expected client change to have saved.' ); } /** * Test what happens when 'type' field isn't present */ public function testNoType() { // journal patch the server so it appears to have a change spec that was locked at 2006.1 $this->p4->run( 'admin', 'import', "@pv@ 0 @db.bodtext@ @change@ 0 @Change;code:201;fmt:L;rq;ro;seq:1;len:10;;Date;code:202;" . "type:date;opt:always;fmt:R;ro;seq:3;len:20;;Client;code:203;opt:always;fmt:L;ro;seq:2;" . "len:32;;User;code:204;opt:always;fmt:L;ro;seq:4;len:32;;Status;code:205;opt:always;" . "fmt:R;ro;seq:5;len:10;;Description;code:206;type:text;opt:required;rq;seq:6;;JobStatus;" . "code:207;type:select;fmt:I;seq:8;;Jobs;code:208;type:wlist;seq:7;len:32;;Files;code:210;" . "type:llist;len:64;;@\n" ); $change = new Change; $this->assertSame(Change::PUBLIC_CHANGE, $change->getType()); } /** * Test a saving with a different user id set */ public function testSaveOtherUser() { // verify we cannot swap to an invalid user $this->assertFalse(User::exists('swapped')); $change = Change::fetch('default'); try { $change->setDescription('Test submit') ->setUser('swapped') ->save(); $this->fail('should not have worked'); } catch (CommandException $e) { $this->assertSame( "Command failed: Error in change specification.\nUser swapped doesn't exist.", $e->getMessage() ); } // add the user swapped to make later attempts work $user = new User; $user->setId('swapped')->setEmail('foo@bar.com')->setFullName('Swapped User')->save(); // verify set user works on existing change $change = Change::fetch('default'); $change->setDescription('Test submit2') ->save(); $this->assertSame('tester', Change::fetch(1)->getUser()); $change = Change::fetch(1); $change->setUser('swapped')->save(); $this->assertSame('swapped', Change::fetch(1)->getUser()); // verify out of the gate set user works $change = Change::fetch('default'); $change->setDescription('Test submit') ->setUser('swapped') ->save(); $this->assertSame('swapped', Change::fetch(2)->getUser()); } /** * Test canAccess() method. */ public function testCanAccess() { // create user 'foo' with limited access to depot $p4Foo = $this->connectWithAccess('foo', array('//depot/foo/...')); // create few changes to test with $file = new File; $file->setFilespec('//depot/test1')->open()->setLocalContents('abc'); $change = new Change($this->p4); $change->setType(Change::RESTRICTED_CHANGE)->addFile($file)->submit('restricted #1'); // NOT accessible by 'foo' $id1 = $change->getId(); $file = new File; $file->setFilespec('//depot/foo/test3')->open()->setLocalContents('def'); $change = new Change($this->p4); $change->setType(Change::RESTRICTED_CHANGE)->addFile($file)->submit('restricted #2'); // accessible by 'foo' $id2 = $change->getId(); $file = new File; $file->setFilespec('//depot/test2')->open()->setLocalContents('ghi'); $change = new Change($this->p4); $change->setType(Change::PUBLIC_CHANGE)->addFile($file)->submit('public'); // accessible by 'foo' $id3 = $change->getId(); // for users other than 'foo', all changes should be accessible $this->assertTrue(Change::fetch($id1, $this->p4)->canAccess()); $this->assertTrue(Change::fetch($id2, $this->p4)->canAccess()); $this->assertTrue(Change::fetch($id3, $this->p4)->canAccess()); // ensure user 'foo' cannot access change id1, but can access the others $this->assertFalse(Change::fetch($id1, $p4Foo)->canAccess()); $this->assertTrue(Change::fetch($id2, $p4Foo)->canAccess()); $this->assertTrue(Change::fetch($id3, $p4Foo)->canAccess()); } public function testGetFileData() { $files = array(); for ($i = 0; $i < 10; $i++) { $file = new File; $files[] = $file; $file->setFilespec('//depot/' . $i) ->add() ->setLocalContents('contents-' . $i); } $change = new Change; $change->setDescription("Test change") ->setFiles($files) ->save('test'); $change = Change::fetch(1); $files = $change->getFileData(); $this->assertSame(10, count($files)); $this->assertSame('//depot/0', current(current($files))); // test max limiting $files = $change->getFileData(null, 5); $this->assertSame(5, count($files)); // test that shelved/unshelved files get cached separately $files = $change->getFileData(true); $this->assertSame(0, count($files)); } }