Add CPOperation to Foundation

This commit is contained in:
Ross Boucher committed 2009-12-23 11:30:10 -05:00
1 parent cb1cfa4d94
commit 6cbaa35849
9 files changed
+1200

No files matched your search

+85
View File
@@ -0,0 +1,85 @@
/*
* CPFunctionOperation.j
*
* Created by Johannes Fahrenkrug.
* Copyright 2009, Springenwerk.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
@import <Foundation/CPObject.j>
@import "CPOperation.j"
/*!
@class CPFunctionOperation
@brief Represents an operation using a JavaScript function that can be run in an CPOperationQueue
*/
@implementation CPFunctionOperation : CPOperation
{
CPArray _functions;
}
- (void)main
{
if (_functions && [_functions count] > 0)
{
var i = 0;
for (i = 0; i < [_functions count]; i++)
{
var func = [_functions objectAtIndex:i];
func();
}
}
}
- (id)init
{
if (self = [super init])
{
_functions = [];
}
return self;
}
/*!
Adds the specified JS function to the receiver’s list of functions to perform.
*/
- (void)addExecutionFunction:(JSObject)jsFunction
{
[_functions addObject:jsFunction];
}
/*!
Returns an array containing the functions associated with the receiver.
*/
- (CPArray)executionFunctions
{
return _functions;
}
/*!
Creates and returns an NSFunctionOperation object and adds the specified function to it.
*/
+ (id)functionOperationWithFunction:(JSObject)jsFunction
{
functionOp = [[CPFunctionOperation alloc] init];
[functionOp addExecutionFunction:jsFunction];
return functionOp;
}
@end
+107
View File
@@ -0,0 +1,107 @@
/*
* CPInvocationOperation.j
*
* Created by Johannes Fahrenkrug.
* Copyright 2009, Springenwerk.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
@import <Foundation/CPObject.j>
@import <Foundation/CPInvocation.j>
@import "CPOperation.j"
/*!
@class CPInvocationOperation
@brief Represents an operation using an invocation that can be run in an CPOperationQueue
*/
@implementation CPInvocationOperation : CPOperation
{
CPInvocation _invocation;
}
- (void)main
{
if (_invocation)
{
[_invocation invoke];
}
}
- (id)init
{
if (self = [super init])
{
_invocation = nil;
}
return self;
}
/*!
Returns a CPInvocationOperation object initialized with the specified invocation object.
@param inv the invocation
*/
- (id)initWithInvocation:(CPInvocation)inv
{
if (self = [self init])
{
_invocation = inv;
}
return self;
}
/*!
Returns an NSInvocationOperation object initialized with the specified target and selector.
@param target the target
@param sel the selector that should be called on the target
@param arg the arguments
*/
- (id)initWithTarget:(id)target selector:(SEL)sel object:(id)arg
{
var inv = [[CPInvocation alloc] initWithMethodSignature:nil];
[inv setTarget:target];
[inv setSelector:sel];
[inv setArgument:arg atIndex:2];
return [self initWithInvocation:inv];
}
/*!
Returns the receiver’s invocation object.
*/
- (CPInvocation)invocation
{
return _invocation;
}
/*!
Returns the result of the invocation or method.
*/
- (id)result
{
if ([self isFinished] && _invocation)
{
return [_invocation returnValue];
}
return nil;
}
@end
+293
View File
@@ -0,0 +1,293 @@
/*
* CPOperation.j
*
* Created by Johannes Fahrenkrug.
* Copyright 2009, Springenwerk.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
@import <Foundation/CPObject.j>
/*!
Operations receive very low priority for execution.
@global
@group CPOperationQueuePriority
*/
CPOperationQueuePriorityVeryLow = -8;
/*!
Operations receive low priority for execution.
@global
@group CPOperationQueuePriority
*/
CPOperationQueuePriorityLow = -4;
/*!
Operations receive normal priority for execution.
@global
@group CPOperationQueuePriority
*/
CPOperationQueuePriorityNormal = 0;
/*!
Operations receive high priority for execution.
@global
@group CPOperationQueuePriority
*/
CPOperationQueuePriorityHigh = 4;
/*!
Operations receive very high priority for execution.
@global
@group CPOperationQueuePriority
*/
CPOperationQueuePriorityVeryHigh = 8;
/*!
@class CPOperation
@brief Represents an operation that can be run in an CPOperationQueue
It should be subclassed an the subclass should implement its own main method to do the actual work.
*/
@implementation CPOperation : CPObject
{
CPArray operations;
BOOL _cancelled;
BOOL _executing;
BOOL _finished;
BOOL _ready;
int _queuePriority;
JSObject _completionFunction;
CPArray _dependencies;
}
- (void)main
{
// should be overridden in child class
}
- (id)init
{
if (self = [super init])
{
_cancelled = NO;
_executing = NO;
_finished = NO;
_ready = YES;
_dependencies = [[CPArray alloc] init];
_queuePriority = CPOperationQueuePriorityNormal;
}
return self;
}
/*!
Starts the operation (runs the main method), sets all the status flags and runs the completion function if it's set
*/
- (void)start
{
if (!_cancelled)
{
[self willChangeValueForKey:@"isExecuting"];
_executing = YES;
[self didChangeValueForKey:@"isExecuting"];
[self main];
if (_completionFunction)
{
_completionFunction();
}
[self willChangeValueForKey:@"isExecuting"];
_executing = NO;
[self didChangeValueForKey:@"isExecuting"];
[self willChangeValueForKey:@"isFinished"];
_finished = YES;
[self didChangeValueForKey:@"isFinished"];
}
}
/*!
Indicates if this operation has been cancelled
@return if this operation has been cancelled
*/
- (BOOL)isCancelled
{
return _cancelled;
}
/*!
Indicates if this operation is currently executing
@return if this operation is currently executing
*/
- (BOOL)isExecuting
{
return _executing;
}
/*!
Indicates if this operation has finished running
@return if this operation has finished running
*/
- (BOOL)isFinished
{
return _finished;
}
/*!
Just added for Cocoa compatibility
@return always false
*/
- (BOOL)isConcurrent
{
return NO;
}
/*!
Indicates if this operation is ready to be executed. Takes the "isFinished" state of dependent operations into account
@return if this operation is ready to run
*/
- (BOOL)isReady
{
return _ready;
}
/*!
The JS function that should be run after the main method
@return JS function
*/
– (JSObject)completionFunction
{
return _completionFunction;
}
/*!
Sets the JS function that should be run after the main method
*/
- (void)setCompletionFunction:(JSObject)aJavaScriptFunction
{
_completionFunction = aJavaScriptFunction;
}
/*!
Makes the receiver dependent on the completion of the specified operation.
@param anOperation the operation that the receiver should depend on
*/
- (void)addDependency:(CPOperation)anOperation
{
[self willChangeValueForKey:@"dependencies"];
[anOperation addObserver:self
forKeyPath:@"isFinished"
options:(CPKeyValueObservingOptionNew)
context:NULL];
[_dependencies addObject:anOperation];
[self didChangeValueForKey:@"dependencies"];
[self _updateIsReadyState];
}
/*!
Removes the receiver’s dependence on the specified operation.
@param anOperation the operation that the receiver should no longer depend on
*/
- (void)removeDependency:(CPOperation)anOperation
{
[self willChangeValueForKey:@"dependencies"];
[_dependencies removeObject:anOperation];
[anOperation removeObserver:self
forKeyPath:@"isFinished"];
[self didChangeValueForKey:@"dependencies"];
[self _updateIsReadyState];
}
/*!
The operations that the receiver depends on
@return array of operations
*/
- (CPArray)dependencies
{
return _dependencies;
}
/*!
Just added for Cocoa compatibility, doesn't do anything
*/
- (void)waitUntilFinished
{
}
/*!
Advises the operation object that it should stop executing its task.
*/
- (void)cancel
{
[self willChangeValueForKey:@"isCancelled"];
_cancelled = YES;
[self didChangeValueForKey:@"isCancelled"];
}
/*!
Sets the priority of the operation when used in an operation queue.
@param priority the priority
*/
- (void)setQueuePriority:(int)priority
{
_queuePriority = priority;
}
/*!
The priority of the operation when used in an operation queue.
@return the priority
*/
- (int)queuePriority
{
return _queuePriority;
}
// We need to observe the "isFinished" key of our dependent operations so we can update our own "isReady" state
- (void)observeValueForKeyPath:(CPString)keyPath
ofObject:(id)object
change:(CPDictionary)change
context:(void)context
{
if (keyPath == @"isFinished")
{
[self _updateIsReadyState];
}
}
- (void)_updateIsReadyState
{
var newReady = YES;
if (_dependencies && [_dependencies count] > 0)
{
var i = 0;
for (i = 0; i < [_dependencies count]; i++)
{
if (![[_dependencies objectAtIndex:i] isFinished])
{
newReady = NO;
}
}
}
if (newReady != _ready)
{
[self willChangeValueForKey:@"isReady"];
_ready = newReady;
[self didChangeValueForKey:@"isReady"];
}
}
@end
+293
View File
@@ -0,0 +1,293 @@
/*
* CPOperationQueue.j
*
* Created by Johannes Fahrenkrug.
* Copyright 2009, Springenwerk.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
@import <Foundation/CPObject.j>
@import "CPOperation.j"
@import "CPInvocationOperation.j"
@import "CPFunctionOperation.j"
// the global queue (mainQueue)
var cpOperationMainQueue = nil;
/*!
@class CPOperationQueue
@brief Represents an operation queue that can run CPOperations
*/
@implementation CPOperationQueue : CPObject
{
CPArray _operations;
BOOL _suspended;
CPString _name @accessors(property=name);
CPTimer _timer;
}
- (id)init
{
if (self = [super init])
{
_operations = [[CPArray alloc] init];
_suspended = NO;
_currentlyModifyingOps = NO;
_timer = [CPTimer scheduledTimerWithTimeInterval:0.01
target:self
selector:@selector(_runNextOpsInQueue)
userInfo:nil
repeats:YES];
}
return self;
}
- (void)_runNextOpsInQueue
{
if (!_suspended && [self operationCount] > 0)
{
var i = 0;
for (i = 0; i < [_operations count]; i++)
{
var op = [_operations objectAtIndex:i];
if ([op isReady] && ![op isCancelled] && ![op isFinished] && ![op isExecuting])
{
[op start];
}
}
}
}
- (void)_enableTimer:(BOOL)enable
{
if (!enable)
{
if (_timer)
{
[_timer invalidate];
_timer = nil;
}
}
else
{
if (!_timer)
{
_timer = [CPTimer scheduledTimerWithTimeInterval:0.01
target:self
selector:@selector(_runNextOpsInQueue)
userInfo:nil
repeats:YES];
}
}
}
/*!
Adds the specified operation object to the receiver.
@param anOperation the operation that should be scheduled for execution
*/
- (void)addOperation:(CPOperation)anOperation
{
[self willChangeValueForKey:@"operations"];
[self willChangeValueForKey:@"operationCount"];
[_operations addObject:anOperation];
[self _sortOpsByPriority:_operations];
[self didChangeValueForKey:@"operations"];
[self didChangeValueForKey:@"operationCount"];
}
/*!
Adds the specified array of operations to the queue.
@param ops The array of CPOperation objects that you want to add to the receiver.
@param wait If YES, the method only returns once all of the specified operations finish executing. If NO, the operations are added to the queue and control returns immediately to the caller.
*/
- (void)addOperations:(CPArray)ops waitUntilFinished:(BOOL)wait
{
if (ops)
{
if (wait)
{
[self _sortOpsByPriority:ops];
[self _runOpsSynchronously:ops];
}
[_operations addObjectsFromArray:ops];
[self _sortOpsByPriority:_operations];
}
}
/*!
Wraps the given js function in a CPOperation and adds it to the queue
@param aFunction the JS function to add
*/
- (void)addOperationWithFunction:(JSObject)aFunction
{
[self addOperation:[CPFunctionOperation functionOperationWithFunction:aFunction]];
}
- (CPArray)operations
{
return _operations;
}
- (int)operationCount
{
if (_operations)
{
return [_operations count];
}
return 0;
}
/*!
Cancels all queued and executing operations.
*/
- (void)cancelAllOperations
{
if (_operations)
{
var i = 0;
for (i = 0; i < [_operations count]; i++)
{
[[_operations objectAtIndex:i] cancel];
}
}
}
/*!
Blocks until all of the receiver’s queued and executing operations finish executing.
*/
- (void)waitUntilAllOperationsAreFinished
{
// lets first stop the timer so it won't interfere
[self _enableTimer:NO];
[self _runOpsSynchronously:_operations];
if (!_suspended)
{
[self _enableTimer:YES];
}
}
/*!
Returns the maximum number of concurrent operations that the receiver can execute.
Always returns 1 because JS doesn't have threads
*/
- (int)maxConcurrentOperationCount
{
return 1;
}
/*!
Modifies the execution of pending operations
@param suspend if YES, queue execution is suspended. If NO, it is resumed
*/
- (void)setSuspended:(BOOL)suspend
{
_suspended = suspend;
[self _enableTimer:!suspend];
}
/*!
Returns a Boolean value indicating whether the receiver is scheduling queued operations for execution.
*/
- (BOOL)isSuspended
{
return _suspended;
}
- (void)_sortOpsByPriority:(CPArray)someOps
{
if (someOps)
{
[someOps sortUsingFunction:function(lhs, rhs)
{
if ([lhs queuePriority] < [rhs queuePriority])
{
return 1;
}
else
{
if ([lhs queuePriority] > [rhs queuePriority])
{
return -1;
}
else
{
return 0;
}
}
}
context:nil];
}
}
- (void)_runOpsSynchronously:(CPArray)ops
{
if (ops)
{
var keepGoing = YES;
while (keepGoing)
{
var i = 0;
keepGoing = NO;
// start the ones that are ready
for (i = 0; i < [ops count]; i++)
{
var op = [ops objectAtIndex:i];
if ([op isReady] && ![op isCancelled] && ![op isFinished] && ![op isExecuting])
{
[op start];
}
}
// make sure they are all done
for (i = 0; i < [ops count]; i++)
{
var op = [ops objectAtIndex:i];
if (![op isFinished] && ![op isCancelled])
{
keepGoing = YES;
}
}
}
}
}
/*!
Convenience method for one system wide singelton queue. Returns the same queue as currentQueue.
*/
+ (CPOperationQueue)mainQueue
{
if (!cpOperationMainQueue)
{
cpOperationMainQueue = [[CPOperationQueue alloc] init];
[cpOperationMainQueue setName:@"main"];
}
return cpOperationMainQueue;
}
/*!
Convenience method for one system wide singelton queue. Returns the same queue as mainQueue.
*/
+ (CPOperationQueue)currentQueue
{
return [CPOperationQueue mainQueue];
}
@end
+2
View File
@@ -42,6 +42,8 @@
@import "CPNumber.j"
@import "CPObject.j"
@import "CPObjJRuntime.j"
@import "CPOperation.j"
@import "CPOperationQueue.j"
@import "CPPropertyListSerialization.j"
@import "CPRange.j"
@import "CPRunLoop.j"
@@ -0,0 +1,33 @@
@import <Foundation/CPFunctionOperation.j>
@implementation SomeObject : CPObject
{
CPString result @accessors;
}
- (CPString)setAString:(CPString)someString
{
result = someString;
return @"Done";
}
@end
@implementation CPFunctionOperationTest : OJTestCase
- (void)testRunInvocation
{
so = [[SomeObject alloc] init];
var someVar = nil;
funcOp = [CPFunctionOperation functionOperationWithFunction:function() {[so setAString:@"Hello World"]}];
[funcOp addExecutionFunction:function() {someVar = 'Soylent Green';}];
[funcOp start];
[self assert:@"Hello World" equals:[so result]];
[self assert:@"Soylent Green" equals:someVar];
}
@end
@@ -0,0 +1,30 @@
@import <Foundation/CPInvocationOperation.j>
@implementation SomeObject : CPObject
{
CPString result @accessors;
}
- (CPString)setAString:(CPString)someString
{
result = someString;
return @"Done";
}
@end
@implementation CPInvocationOperationTest : OJTestCase
- (void)testRunInvocation
{
var so = [[SomeObject alloc] init],
io = [[CPInvocationOperation alloc] initWithTarget:so selector:@selector(setAString:) object:@"Hello World"];
[io start];
[self assert:@"Hello World" equals:[so result]];
[self assert:@"Done" equals:[io result]];
}
@end
+172
View File
@@ -0,0 +1,172 @@
@import <Foundation/CPOperationQueue.j>
globalResults = [];
@implementation TestOperation : CPOperation
{
CPString name @accessors;
CPString value @accessors;
}
- (void)main
{
[self setName:@"test"];
globalResults.push([self value]);
}
@end
@implementation TestObserver : CPObject
{
CPArray changedKeyPaths @accessors;
}
- (id)init
{
if (self = [super init])
{
changedKeyPaths = [[CPArray alloc] init];
}
return self;
}
// KVO change notification
- (void)observeValueForKeyPath:(CPString)keyPath
ofObject:(id)object
change:(CPDictionary)change
context:(void)context
{
[changedKeyPaths addObject:keyPath];
}
@end
@implementation CPOperationQueueTest : OJTestCase
- (void)testAddOperation
{
var oq = [[CPOperationQueue alloc] init],
to = [[TestOperation alloc] init];
[self assert:0 equals:[oq operationCount]];
[oq addOperation:to];
[self assert:1 equals:[oq operationCount]];
}
- (void)testAddOperationsWithWaitUntilFinished
{
var oq = [[CPOperationQueue alloc] init],
to1 = [[TestOperation alloc] init],
to2 = [[TestOperation alloc] init],
to3 = [[TestOperation alloc] init];
[to3 addDependency:to1];
[to3 addDependency:to2];
[self assert:0 equals:[oq operationCount]];
[oq addOperations:[to1, to2, to3] waitUntilFinished:YES];
//make sure they all ran and are finished...
[self assertTrue:[to1 isFinished]];
[self assertTrue:[to2 isFinished]];
[self assertTrue:[to3 isFinished]];
[self assert:@"test" equals:[to1 name]];
[self assert:@"test" equals:[to2 name]];
[self assert:@"test" equals:[to3 name]];
[self assert:3 equals:[oq operationCount]];
}
- (void)testRunOperationsInCorrectOrder
{
var oq = [[CPOperationQueue alloc] init],
to1 = [[TestOperation alloc] init];
[to1 setQueuePriority:CPOperationQueuePriorityVeryLow];
[to1 setValue:@"very low"];
var to2 = [[TestOperation alloc] init];
[to2 setQueuePriority:CPOperationQueuePriorityVeryHigh];
[to2 setValue:@"very high"];
var to3 = [[TestOperation alloc] init];
[to3 setValue:@"normal"]
var to4 = [[TestOperation alloc] init];
[to4 setQueuePriority:CPOperationQueuePriorityLow];
[to4 setValue:@"low"];
var to5 = [[TestOperation alloc] init];
[to5 setQueuePriority:CPOperationQueuePriorityHigh];
[to5 setValue:@"high"];
var to6 = [[TestOperation alloc] init];
[to6 setValue:@"also normal"];
globalResults = [];
[oq addOperations:[to3, to4, to5, to6] waitUntilFinished:NO];
[oq waitUntilAllOperationsAreFinished];
[self assertTrue:[to3 isFinished]];
[self assertTrue:[to4 isFinished]];
[self assertTrue:[to5 isFinished]];
[self assertTrue:[to6 isFinished]];
[self assert:@"high" equals:globalResults[0]];
[self assert:@"normal" equals:globalResults[1]];
[self assert:@"also normal" equals:globalResults[2]];
[self assert:@"low" equals:globalResults[3]];
globalResults = [];
[oq addOperation:to1];
[oq addOperation:to2];
[oq waitUntilAllOperationsAreFinished];
[self assert:@"very high" equals:globalResults[0]];
[self assert:@"very low" equals:globalResults[1]];
}
- (void)testAddOperationWithFunction
{
var oq = [[CPOperationQueue alloc] init];
globalResults = [];
[oq addOperationWithFunction:function() {globalResults.push("Soylent");}];
[oq waitUntilAllOperationsAreFinished];
[self assert:@"Soylent" equals:globalResults[0]];
}
- (void)testKVO
{
var oq = [[CPOperationQueue alloc] init],
obs = [[TestObserver alloc] init];
[oq addObserver:obs
forKeyPath:@"operations"
options:(CPKeyValueObservingOptionNew)
context:NULL];
[oq addObserver:obs
forKeyPath:@"operationCount"
options:(CPKeyValueObservingOptionNew)
context:NULL];
[oq addObserver:obs
forKeyPath:@"suspended"
options:(CPKeyValueObservingOptionNew)
context:NULL];
[oq addObserver:obs
forKeyPath:@"name"
options:(CPKeyValueObservingOptionNew)
context:NULL];
[oq addOperationWithFunction:function() {globalResults.push("Soylent");}];
[self assert:@"operations" equals:[[obs changedKeyPaths] objectAtIndex:0]];
[self assert:@"operationCount" equals:[[obs changedKeyPaths] objectAtIndex:1]];
[oq setSuspended:YES];
[self assert:@"suspended" equals:[[obs changedKeyPaths] objectAtIndex:2]];
[oq setSuspended:NO];
[self assert:@"suspended" equals:[[obs changedKeyPaths] objectAtIndex:3]];
[oq setName:@"Soylent"];
[self assert:@"name" equals:[[obs changedKeyPaths] objectAtIndex:4]];
}
@end
+185
View File
@@ -0,0 +1,185 @@
@import <Foundation/CPOperation.j>
@implementation TestOperation : CPOperation
{
CPString name @accessors;
CPString value @accessors;
}
- (void)main
{
[self setName:@"test"];
}
@end
@implementation TestObserver : CPObject
{
CPArray changedKeyPaths @accessors;
}
- (id)init
{
if (self = [super init])
{
changedKeyPaths = [[CPArray alloc] init];
}
return self;
}
// KVO change notification
- (void)observeValueForKeyPath:(CPString)keyPath
ofObject:(id)object
change:(CPDictionary)change
context:(void)context
{
[changedKeyPaths addObject:keyPath];
}
@end
@implementation CPOperationTest : OJTestCase
- (void)testAddDependency
{
var co = [[CPOperation alloc] init],
co_dep = [[CPOperation alloc] init];
[co addDependency:co_dep];
[self assert:1 equals:[[co dependencies] count]];
}
- (void)testRemoveDependency
{
var co = [[CPOperation alloc] init],
co_dep1 = [[CPOperation alloc] init],
co_dep2 = [[CPOperation alloc] init];
[co addDependency:co_dep1];
[co addDependency:co_dep2];
[self assert:2 equals:[[co dependencies] count]];
[co removeDependency:co_dep2];
[self assert:1 equals:[[co dependencies] count]];
[co removeDependency:co_dep2];
[self assert:1 equals:[[co dependencies] count]];
[co removeDependency:co_dep1];
[self assert:0 equals:[[co dependencies] count]];
}
- (void)testCorrectValuesOnInit
{
var co = [[CPOperation alloc] init];
[self assertTrue:[co isReady]];
[self assertFalse:[co isCancelled]];
[self assertFalse:[co isConcurrent]];
[self assertFalse:[co isFinished]];
[self assertFalse:[co isExecuting]];
[self assert:CPOperationQueuePriorityNormal equals:[co queuePriority]];
}
- (void)testIsReadyWithDependencies
{
var co = [[CPOperation alloc] init],
co_dep1 = [[CPOperation alloc] init],
co_dep2 = [[CPOperation alloc] init];
[self assertTrue:[co isReady]];
[co addDependency:co_dep1];
[co addDependency:co_dep2];
[self assertFalse:[co isReady]];
[co_dep1 start];
[self assertFalse:[co isReady]];
[co_dep2 start];
[self assertTrue:[co isReady]];
}
- (void)testCompletionFunction
{
var to = [[TestOperation alloc] init];
[to setCompletionFunction:function() {[to setValue:@"something"];}];
[to start];
[self assert:@"something" equals:[to value]];
}
// KVO Tests
- (void)testKVO
{
var to = [[TestOperation alloc] init],
to2 = [[TestOperation alloc] init],
obs = [[TestObserver alloc] init];
[to addObserver:obs
forKeyPath:@"isCancelled"
options:(CPKeyValueObservingOptionNew)
context:NULL];
[to addObserver:obs
forKeyPath:@"isExecuting"
options:(CPKeyValueObservingOptionNew)
context:NULL];
[to addObserver:obs
forKeyPath:@"isFinished"
options:(CPKeyValueObservingOptionNew)
context:NULL];
[to addObserver:obs
forKeyPath:@"isReady"
options:(CPKeyValueObservingOptionNew)
context:NULL];
[to addObserver:obs
forKeyPath:@"dependencies"
options:(CPKeyValueObservingOptionNew)
context:NULL];
[to addObserver:obs
forKeyPath:@"queuePriority"
options:(CPKeyValueObservingOptionNew)
context:NULL];
[to addObserver:obs
forKeyPath:@"completionFunction"
options:(CPKeyValueObservingOptionNew)
context:NULL];
[to addDependency:to2];
[self assert:@"dependencies" equals:[[obs changedKeyPaths] objectAtIndex:0]];
[self assert:@"isReady" equals:[[obs changedKeyPaths] objectAtIndex:1]];
[to2 start];
[self assert:@"isReady" equals:[[obs changedKeyPaths] objectAtIndex:2]];
[to removeDependency:to2];
[self assert:@"dependencies" equals:[[obs changedKeyPaths] objectAtIndex:3]];
[to setQueuePriority:CPOperationQueuePriorityHigh];
[self assert:@"queuePriority" equals:[[obs changedKeyPaths] objectAtIndex:4]];
[to setCompletionFunction:function() {}];
[self assert:@"completionFunction" equals:[[obs changedKeyPaths] objectAtIndex:5]];
// this should set executing = yes, executing = no, finished = yes.
[to start];
[self assert:@"isExecuting" equals:[[obs changedKeyPaths] objectAtIndex:6]];
[self assert:@"isExecuting" equals:[[obs changedKeyPaths] objectAtIndex:7]];
[self assert:@"isFinished" equals:[[obs changedKeyPaths] objectAtIndex:8]];
[to cancel];
[self assert:@"isCancelled" equals:[[obs changedKeyPaths] objectAtIndex:9]];
}
@end