Dependencies

- Added check for circular dependencies.
This commit is contained in:
Paul Beckingham 2010-08-26 22:52:57 -04:00
parent 58d678f927
commit 0e2c090dc5
5 changed files with 78 additions and 30 deletions

View file

@ -493,6 +493,9 @@ void Task::addDependency (int id)
} }
else else
set ("depends", uuid); set ("depends", uuid);
if (dependencyIsCircular (*this))
throw std::string ("Circular dependency detected and disallowed.");
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////

View file

@ -24,7 +24,11 @@
// USA // USA
// //
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
#include <algorithm>
#include <iostream>
#include <Context.h> #include <Context.h>
#include <text.h>
extern Context context; extern Context context;
@ -34,6 +38,7 @@ extern Context context;
// void dependencyRepairChain (); // void dependencyRepairChain ();
// bool dependencyRepairConfirm (); // bool dependencyRepairConfirm ();
// void dependencyNag (); // void dependencyNag ();
static bool followUpstream (const Task&, const Task&, const std::vector <Task>&, std::vector <std::string>&);
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// All it takes to be blocked is to depend on another task. // All it takes to be blocked is to depend on another task.
@ -62,35 +67,72 @@ bool dependencyIsBlocking (Task& task)
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// Follow each of the given task's dependencies to the end of the chain, and if // Terminology:
// any duplicates show up, or the chain length exceeds N, stop. // --> if a depends on b, then it can be said that a --> b
// Head if a --> b, then b is the head
/* // Tail if a --> b, then a is the tail
Linear: //
1->2 // Algorithm:
// Find all tails, ie tasks that have dependencies, with no other tasks that
1->2->3->4 // are dependent on them.
`->5->6 //
`->7 // For each tail:
// follow the chain, recording all linkages, ie a --> b, b --> c. If a
Circular: // linkage appears that has already occurred in this chain => circularity.
1->1 //
1->2->1
1->2->3
`->1
Algorithm:
1. Generate a subset of all task that have dependencies
2. Find the heads of all the chains
3. For each unique chain head
3.1 Walk the chain recording IDs
3.2 Duplicate ID => circular
*/
bool dependencyIsCircular (Task& task) bool dependencyIsCircular (Task& task)
{ {
std::vector <std::string> links;
const std::vector <Task>& all = context.tdb.getAllPending ();
return followUpstream (task, task, all, links);
}
////////////////////////////////////////////////////////////////////////////////
// To follow dependencies upstream, follow the heads.
static bool followUpstream (
const Task& task,
const Task& original,
const std::vector <Task>& all,
std::vector <std::string>& links)
{
if (task.has ("depends"))
{
std::vector <std::string> uuids;
split (uuids, task.get ("depends"), ',');
std::vector <std::string>::iterator outer;
for (outer = uuids.begin (); outer != uuids.end (); ++outer)
{
// Check that link has not already been seen.
std::string link = task.get ("uuid") + " -> " + *outer;
if (std::find (links.begin (), links.end (), link) != links.end ())
return true;
links.push_back (link);
// Recurse up the chain.
std::vector <Task>::const_iterator inner;
for (inner = all.begin (); inner != all.end (); ++inner)
{
if (*outer == inner->get ("uuid"))
{
if (*outer == original.get ("uuid"))
{
if (followUpstream (task, original, all, links))
return true;
}
else
{
if (followUpstream (*inner, original, all, links))
return true;
}
break;
}
}
}
}
return false; return false;
} }

View file

@ -133,7 +133,7 @@ int handleExportYAML (std::string &);
// dependency.cpp // dependency.cpp
bool dependencyIsBlocked (Task&); bool dependencyIsBlocked (Task&);
bool dependencyIsBlocking (Task&); bool dependencyIsBlocking (Task&);
bool dependencyCheckCircular (Task&); bool dependencyIsCircular (Task&);
// list template // list template
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////

View file

@ -24,6 +24,7 @@
// USA // USA
// //
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
#include <iostream> #include <iostream>
#include <iomanip> #include <iomanip>
#include <fstream> #include <fstream>

View file

@ -28,7 +28,7 @@
use strict; use strict;
use warnings; use warnings;
use Test::More tests => 35; use Test::More tests => 37;
# Create the rc file. # Create the rc file.
if (open my $fh, '>', 'dep.rc') if (open my $fh, '>', 'dep.rc')
@ -73,6 +73,7 @@ like ($output, qr/Modified 0 tasks\./, 'dependencies - add already existing depe
# t 1 dep:2; t 2 dep:1 => error # t 1 dep:2; t 2 dep:1 => error
$output = qx{../task rc:dep.rc 2 dep:1}; $output = qx{../task rc:dep.rc 2 dep:1};
like ($output, qr/Circular dependency detected and disallowed\./, 'dependencies - trivial circular');
unlike ($output, qr/Modified 1 task\./, 'dependencies - trivial circular'); unlike ($output, qr/Modified 1 task\./, 'dependencies - trivial circular');
unlink 'pending.data'; unlink 'pending.data';
@ -88,6 +89,7 @@ qx{../task rc:dep.rc 5 dep:4; ../task rc:dep.rc 4 dep:3; ../task rc:dep.rc 3 dep
# 5 dep 4 dep 3 dep 2 dep 1 dep 5 => error # 5 dep 4 dep 3 dep 2 dep 1 dep 5 => error
$output = qx{../task rc:dep.rc 1 dep:5}; $output = qx{../task rc:dep.rc 1 dep:5};
like ($output, qr/Circular dependency detected and disallowed\./, 'dependencies - nontrivial circular');
unlike ($output, qr/Modified 1 task\./, 'dependencies - nontrivial circular'); unlike ($output, qr/Modified 1 task\./, 'dependencies - nontrivial circular');
unlink 'pending.data'; unlink 'pending.data';