Added rantest and cqtool to repo
authorAndrew DeFaria <Andrew@DeFaria.com>
Thu, 9 Nov 2017 04:34:23 +0000 (20:34 -0800)
committerAndrew DeFaria <Andrew@DeFaria.com>
Thu, 9 Nov 2017 04:34:23 +0000 (20:34 -0800)
22 files changed:
cqtool/CreateHelpDeskUI.pm [new file with mode: 0644]
cqtool/CreateWORUI.pm [new file with mode: 0644]
cqtool/cqtool.pl [new file with mode: 0755]
rantest/East.pm [new file with mode: 0644]
rantest/rantest [new file with mode: 0644]
rantest/web/Rantest/AverageRunTime.php [new file with mode: 0644]
rantest/web/Rantest/ChangeLog.php [new file with mode: 0644]
rantest/web/Rantest/ChangeLog0.9.php [new file with mode: 0644]
rantest/web/Rantest/ChangeLog1.0.php [new file with mode: 0644]
rantest/web/Rantest/ChangeLog1.1.php [new file with mode: 0644]
rantest/web/Rantest/FailureAnalysis.php [new file with mode: 0644]
rantest/web/Rantest/GraphStats.php [new file with mode: 0644]
rantest/web/Rantest/TestHistory.php [new file with mode: 0644]
rantest/web/Rantest/TestStats.php [new file with mode: 0644]
rantest/web/Rantest/TestcasePerVersion.php [new file with mode: 0644]
rantest/web/Rantest/VersionPerTestcase.php [new file with mode: 0644]
rantest/web/Rantest/index.php [new file with mode: 0644]
rantest/web/Rantest/rantest.php [new file with mode: 0644]
rantest/web/index.php [new file with mode: 0644]
rantest/web/php/RantestDB.php [new file with mode: 0644]
rantest/web/php/Utils.php [new file with mode: 0644]
rantest/web/php/exportToCVS.php [new file with mode: 0644]

diff --git a/cqtool/CreateHelpDeskUI.pm b/cqtool/CreateHelpDeskUI.pm
new file mode 100644 (file)
index 0000000..d3bb60d
--- /dev/null
@@ -0,0 +1,627 @@
+##############################################################################
+#
+# Name: CreateHelpDeskUI.pm
+#
+# Description: CreateHelpDeskUI.pm is a Perl module that encapsulates
+#             a Perl/Tk application to create a Help Desk
+#             ticket. This application was developed for a few
+#             reasons. First ucmwb needs to be able to create Help
+#             Desk tickets. The approach was to use IBM/Rational's
+#             cqtool (/opt/rational/clearquest/bin/cqtool) but there
+#             is two problems with this. First IBM/Rational's cqtool
+#             is unsupported and documented. Secondly IBM/Rational's
+#             cqtool is going away as of Clearquest 7.0.
+#
+#             Another problem is that while IBM/Rational's cqtool
+#             would work, it does not return the ID of the Help Desk ticket
+#             created!
+#
+#             So this Perl/Tk module was created to create Help Desk
+#             tickets. Perl interfaces with Clearquest to call the
+#             appropraite Clearquest action hooks and the like. Note
+#             that only the basic information is asked for. If you
+#             really want to create or modify a full Help Desk ticket
+#             use Clearquest. This Perl/Tk app's main customer is
+#             ucmwb.
+#
+# Author: Andrew@ClearSCM.com
+#
+# (c) Copyright 2007, General Dynamics, all rights reserved
+#
+##############################################################################
+use strict;
+use warnings;
+
+package CreateHelpDeskUI;
+  use Tk;
+  use Tk::Dialog;
+  use Tk::BrowseEntry;
+
+  use Display;
+  use Tk::MyText;
+  use CQTool;
+
+  use base "Exporter";
+
+  my $ME               = "CreateHelpDesk";
+  my $VERSION          = "1.1";
+
+  # Colors
+  my ($EDIT_FOREGROUND, $EDIT_BACKGROUND);
+
+  our %hd;
+
+  our @EXPORT = qw (
+    createHelpDeskUI
+    %hd
+  );
+
+  # Globals
+  my $_createHelpDeskUI;
+
+  # Dropdowns
+  my (
+    $_requestor,
+    $_location,
+    $_category,
+    $_related_version,
+    $_platform,
+    $_requestor_priority,
+  );
+
+  # Choice lists
+  my (
+    @_requestors,
+    @_locations,
+    @_categories,
+    @_related_versions,
+    @_platforms,
+    @_requested_priorities,
+  );
+
+  # Buttons
+  my $_submit;
+
+  ############################################################################
+  # Subroutines
+  ############################################################################
+
+  #---------------------------------------------------------------------------
+  # _helpAbout (): Puts up the Help: About dialog box
+  #---------------------------------------------------------------------------
+  sub _helpAbout () {
+    my $text = "$ME v$VERSION\n";
+
+    $text .= <<END;
+
+This application creates a Help Desk ticket using Perl/Tk. It is used by UCM/WB or can be used stand alone. It effectively replicates the functionality of Clearquest but 1) is blocking and 2) returns the RANCQ-# so that UCM/WB can determine the number of the newly created WOR.
+
+Copyright General Dynamics © 2007 - All rights reserved
+Developed by Andrew DeFaria <Andrew\@ClearSCM.com> of ClearSCM, Inc.
+END
+
+    my $desc = $_createHelpDeskUI->Dialog (
+      -title           => "About $ME",
+      -text            => $text,
+      -buttons         => [ "OK" ],
+    );
+
+    $desc->Show;
+  } # _helpAbout
+
+  #---------------------------------------------------------------------------
+  # _displayValues (): Displays the contents for %hd hash
+  #---------------------------------------------------------------------------
+  sub _displayValues () {
+    foreach (keys %hd) {
+      if ($hd{$_}) {
+        display "$_: $hd{$_}";
+      } else {
+        display "$_: undef";
+      } # if
+    } # foreach
+  } # _displayValues
+
+  #---------------------------------------------------------------------------
+  # _getChoices (): For a given $entity and $fieldname, this routine returns
+  #                the given choice list from Clearquest.
+  #---------------------------------------------------------------------------
+  sub _getChoices ($$) {
+    my ($entity, $fieldname) = @_;
+
+    return @{$entity->GetFieldChoiceList ($fieldname)};
+  } # _getChoices
+
+  #---------------------------------------------------------------------------
+  # _destroyHelpDeskUI (): Destroys the current HelpDesk UI recycling Tk
+  #                       objects
+  #---------------------------------------------------------------------------
+  sub _destroyHelpDeskUI () {
+    # Destroy all globals created
+    destroy $_submit;
+    destroy $_requestor;
+    destroy $_location;
+    destroy $_category;
+    destroy $_related_version;
+    destroy $_platform;
+    destroy $_requestor_priority;
+    destroy $_createHelpDeskUI;
+
+    $_requestor                        =
+    $_location                 =
+    $_category                 =
+    $_related_version          =
+    $_platform                 =
+    $_requestor_priority       =
+    $_submit                   =
+    $_createHelpDeskUI         = undef;
+
+    %hd = ();
+  } # _destroyHelpDeskUI
+
+  #---------------------------------------------------------------------------
+  # _submit (): Actually creates the WOR given the filled out %hd hash.
+  #---------------------------------------------------------------------------
+  sub _submit () {
+    debug "Creating Help Desk Ticket...";
+
+    # Change requestor from a format of "lastname, firstname (badge)" -> badge
+    if ($hd{requestor} =~ /\((\w*)\)$/) {
+      $hd{requestor} = $1;
+    } # if
+
+    _displayValues if get_debug;
+
+    my $new_id = CQTool::submitHelpDesk ($CQTool::entity, %hd);
+
+    display $new_id if $new_id;
+
+    _destroyHelpDeskUI;
+
+    return $new_id;
+  } # _submit
+
+  #---------------------------------------------------------------------------
+  # _setSubmitButton (): Sets the submit button to active only if all required
+  #                     fields have values.
+  #---------------------------------------------------------------------------
+  sub _setSubmitButton (;$) {
+    my ($headline) = @_;
+
+    return if !$_submit;
+
+    # Check to see if we can activate the submit button
+    my $state = "normal";
+
+    foreach (@CQTool::hd_required_fields) {
+      if ($_ eq "headline") {
+        if (defined $headline) {
+         if ($headline eq "") {
+           $state = "disable";
+           last;
+         } else {
+           next;
+         } # if
+       } # if
+      } # if
+
+      if (!$hd{$_} or $hd{$_} eq "") {
+       $state = "disable";
+       last;
+      } # if
+    } # foreach
+
+    $_submit->configure (
+      -state   => $state,
+    );
+  } # _setSubmitButton
+
+  #---------------------------------------------------------------------------
+  # _validateText (): Gets the text from the MyText widget and sets the submit
+  #                  button
+  #---------------------------------------------------------------------------
+  sub _validatetext {
+    my ($text) = @_;
+
+    $hd{description} = $text->get_text;
+    chomp $hd{description};
+
+    _setSubmitButton $text;
+
+    return 1;
+  } # _validatetext
+
+  #---------------------------------------------------------------------------
+  # _validateEntry (): Gets the text from the headline widget and sets the
+  #                   submit button
+  #---------------------------------------------------------------------------
+  sub _validateentry {
+    my ($entry) = @_;
+
+    _setSubmitButton $entry;
+
+    return 1;
+  } # _validateentry
+
+  #---------------------------------------------------------------------------
+  # _createDropDown (): Creates a dropdown widget in $parent in a grid at the
+  #                    $x, $y coordinates with a $label and a $value, using
+  #                    dropdown @values and a $refresh procedure.
+  #---------------------------------------------------------------------------
+  sub _createDropDown ($$$$$$@) {
+    my ($parent, $x, $y, $label, $refresh, $value, @values) = @_;
+
+    $parent->Label (
+      -width           => length $label,
+      -text            => "$label:",
+    )->grid (
+      -row             => $x,
+      -column          => $y,
+      -sticky          => "e",
+    );
+
+    return $parent->Optionmenu (
+      -activeforeground        => $EDIT_FOREGROUND,
+      -activebackground        => $EDIT_BACKGROUND,
+      -command         => \&$refresh,
+      -variable                => $value,
+      -options         => \@values,
+    )->grid (
+      -row             => $x,
+      -column          => $y + 1,
+      -sticky          => "w",
+    );
+  } # _createDropDown
+
+  #---------------------------------------------------------------------------
+  # _createBrowseEntry (): Creates a dropdown like widget which drops down a
+  #                       scrollable list in $parent with a $label, $refresh
+  #                       procedure, setting $value with the choice from
+  #                       @values.
+  #---------------------------------------------------------------------------
+  sub _createBrowseEntry ($$$$$$@) {
+    my ($parent, $x, $y, $label, $refresh, $value, @values) = @_;
+
+    $parent->Label (
+      -width           => length $label,
+      -text            => "$label:",
+    )->grid (
+      -row             => $x,
+      -column          => $y,
+      -sticky          => "e",
+    );
+
+    my $longest_item = 0;
+
+    foreach (@values) {
+      $longest_item = length $_ if length $_ > $longest_item;
+    } # if
+
+    my $browse_entry = $parent->BrowseEntry (
+      -browsecmd       => \&$refresh,
+      -variable                => $value,
+      -width           => $longest_item,
+    )->grid (
+      -row             => $x,
+      -column          => $y + 1,
+      -sticky          => "w",
+    );
+
+    my $i = 0;
+
+    foreach (@values) {
+      $browse_entry->insert ($i++, $_);
+    } # foreach
+
+    return $browse_entry;
+  } # _createBrowseEntry
+
+  #---------------------------------------------------------------------------
+  # _createTextField (): Creates a text field widget in $parent with a $label
+  #                     and a $value, using a $maxlen and a $validate
+  #                     procedure.
+  #---------------------------------------------------------------------------
+  sub _createTextField ($$$$$) {
+    my ($parent, $label, $value, $maxlen, $validate) = @_;
+
+    $parent->Label (
+      -text            => "$label:",
+      -justify         => "right",
+      -width           => 10,
+    )->pack (
+      -side            => "left",
+      -anchor          => "e",
+    );
+
+    $parent->Entry (
+      -foreground      => $EDIT_FOREGROUND,
+      -background      => $EDIT_BACKGROUND,
+      -width           => $maxlen,
+      -justify         => "left",
+      -textvariable    => $value,
+      -validate                => "key",
+      -validatecommand => \&$validate,
+    )->pack (
+      -side            => "left",
+      -padx            => 5,
+      -anchor          => "e",
+    );
+  } # _createTextField
+
+  #---------------------------------------------------------------------------
+  # _createText (): Creates a multiline text field widget in $parent with a
+  #                $label and a $value, using the specified $rows and $cols
+  #                and a $validate procedure.
+  #---------------------------------------------------------------------------
+  sub _createText ($$$$$$) {
+    my ($parent, $label, $value, $rows, $cols, $validate) = @_;
+
+    $parent->Label (
+      -text            => "$label:",
+      -justify         => "right",
+      -width           => 10,
+    )->pack (
+      -side            => "left",+
+      -anchor          => "n",
+      -pady            => 5,
+    );
+
+    $parent->MyText (
+      -foreground      => $EDIT_FOREGROUND,
+      -background      => $EDIT_BACKGROUND,
+      -height          => $rows,
+      -width           => $cols,
+      -modified                => \&$validate,
+      -text            => $value,
+    )->pack (
+      -side            => "left",
+      -pady            => 5,
+      -anchor          => "s",
+    );
+  } # _createText
+
+  #---------------------------------------------------------------------------
+  # _createButton (): Creates a pushbutton widget in $parent with a $label and
+  #                  an $action.
+  #---------------------------------------------------------------------------
+  sub _createButton ($$$) {
+    my ($parent, $label, $action) = @_;
+
+    $parent->Button (
+      -activeforeground        => $EDIT_FOREGROUND,
+      -activebackground        => $EDIT_BACKGROUND,
+      -text            => $label,
+      -width           => length $label,
+    -command           => \$action
+    )->pack (
+      -side            => "left",
+      -padx            => 5
+    );
+  } # _createButton
+
+  #---------------------------------------------------------------------------
+  # _changeDropDown (): Refreshes the values in the dropdown menu.
+  #---------------------------------------------------------------------------
+  sub _changeDropDown ($@) {
+    my ($dropdown, @values) = @_;
+
+    if ($dropdown) {
+      my $menu = $dropdown->menu;
+
+      if ($menu) {
+       $dropdown->menu->delete (0, "end");
+      } # if
+
+      $dropdown->addOptions (@values);
+    } # if
+  } # _changeDropDown
+
+  #---------------------------------------------------------------------------
+  # _refresh (): Refreshes the application by getting news values from
+  #             Clearquest. Note a change in one dropdown may change others,
+  #             so we re-get all of them through this procedure.
+  #---------------------------------------------------------------------------
+  sub _refresh () {
+    my $fieldname;
+
+    $fieldname                         = "category";
+    @_categories               = _getChoices $CQTool::entity, $fieldname;
+    $hd{$fieldname}            = $_categories[0] if !$hd{$fieldname};
+    $CQTool::entity->SetFieldValue ($fieldname, $hd{$fieldname});
+
+    $fieldname                         = "related_version";
+    @_related_versions = _getChoices $CQTool::entity, $fieldname;
+    $hd{$fieldname}            = $_related_versions[0] if !$hd{$fieldname};
+    $CQTool::entity->SetFieldValue ($fieldname, $hd{$fieldname});
+
+    $fieldname                         = "platform";
+    @_platforms                = _getChoices $CQTool::entity, $fieldname;
+    $hd{$fieldname}            = $_platforms[0] if !$hd{$fieldname};
+    $CQTool::entity->SetFieldValue ($fieldname, $hd{$fieldname});
+
+    $fieldname                         = "requestedpriority";
+    @_requested_priorities     = _getChoices $CQTool::entity, $fieldname;
+    $hd{$fieldname}            = $_requested_priorities[0] if !$hd{$fieldname};
+    $CQTool::entity->SetFieldValue ($fieldname, $hd{$fieldname});
+
+    _changeDropDown $_category,                        @_categories;
+    _changeDropDown $_related_version,         @_related_versions;
+    _changeDropDown $_platform,                        @_platforms;
+    _changeDropDown $_requestor_priority,      @_requested_priorities;
+
+    _setSubmitButton;
+  } # _refresh
+
+  #---------------------------------------------------------------------------
+  # _getNames (): Translates an array of badge numbers into a hash of names
+  #              as the key and badge numbers as the value.
+  #---------------------------------------------------------------------------
+  sub _getNames (@) {
+    my (@badges) = @_;
+
+    my %names;
+
+    foreach (@badges) {
+      my $query = $CQTool::session->BuildQuery ("users");
+
+      $query->BuildField ("fullname");
+
+      my $filter = $query->BuildFilterOperator ($CQPerlExt::CQ_BOOL_OP_AND);
+
+      # Clearquest requires values to be in an array
+      my @badge = $_;
+
+      $filter->BuildFilter ("login_name", $CQPerlExt::CQ_COMP_OP_EQ, \@badge);
+
+      my $result = $CQTool::session->BuildResultSet ($query);
+
+      $result->Execute;
+
+      my $status = $result->MoveNext;
+
+      my $fullname;
+
+      while ($status == $CQPerlExt::CQ_SUCCESS) {
+       $fullname = $result->GetColumnValue (1);
+       $status = $result->MoveNext;
+      } # while
+
+      $names{$fullname ? $fullname : "<unknown>"} = $_;
+    } # foreach
+
+    return %names;
+  } # _getNames
+
+  #---------------------------------------------------------------------------
+  # _darken (): Returns a slightly darker color than the passed in color
+  #---------------------------------------------------------------------------
+  sub _darken ($) {
+    my ($color) = @_;
+
+    # Get the RGB values
+    my ($r, $g, $b) = $_createHelpDeskUI->rgb($color);
+
+    # Set them to $DARKEN % of their previous values
+    my $DARKEN = .8;
+    my $rhex = sprintf "%x", $r * $DARKEN;
+    my $ghex = sprintf "%x", $g * $DARKEN;
+    my $bhex = sprintf "%x", $b * $DARKEN;
+
+    # Return a color string
+    return "\#$rhex$ghex$bhex";
+  } # _darken
+
+  #---------------------------------------------------------------------------
+  # _createHelpDeskUI (): This is the main and exported routine that creates
+  #                      and handles the entire Perl/Tk application for
+  #                      creating a Help Desk ticket.
+  #---------------------------------------------------------------------------
+  sub createHelpDeskUI () {
+    $_createHelpDeskUI = MainWindow->new;
+
+    $EDIT_FOREGROUND   = $_createHelpDeskUI->optionGet ("foreground", "Foreground");
+    $EDIT_BACKGROUND   = _darken ($_createHelpDeskUI->optionGet ("background", "Background"));
+
+    $hd{id} = "None" if !$hd{id};
+
+    $_createHelpDeskUI->title ("Submit Helpdesk $hd{id}");
+
+    my $frame0 = $_createHelpDeskUI->Frame->pack (-pady => 2);
+    my $frame1 = $_createHelpDeskUI->Frame->pack;
+    my $frame2 = $_createHelpDeskUI->Frame->pack;
+    my $frame3 = $_createHelpDeskUI->Frame->pack;
+    my $frame4 = $_createHelpDeskUI->Frame->pack;
+    my $frame5 = $_createHelpDeskUI->Frame->pack;
+    my $frame6 = $_createHelpDeskUI->Frame->pack;
+
+    _createTextField
+      $frame1,
+      "Headline",
+      \$hd{headline},
+      100,
+      \&_validateentry;
+
+    _createText
+      $frame2,
+      "Description",
+      \$hd{description},
+      24, 100,
+      \&_validatetext;
+
+    @_categories               = _getChoices $CQTool::entity, "category";
+    @_related_versions = _getChoices $CQTool::entity, "related_version";
+    @_platforms                = _getChoices $CQTool::entity, "platform";
+    @_requested_priorities     = _getChoices $CQTool::entity, "requestedpriority";
+    @_requestors               = _getChoices $CQTool::entity, "requestor";
+
+    my %requestor_names        = _getNames @_requestors;
+
+    @_requestors = ();
+
+    foreach (sort keys %requestor_names) {
+      if ($_ eq "") {
+       push @_requestors, "";
+      } else {
+       push @_requestors, "$_ ($requestor_names{$_})";
+      } # if
+    } # foreach
+
+    @_locations                = _getChoices $CQTool::entity, "requestorlocation";
+
+    $_requestor = _createBrowseEntry
+      $frame3,
+      0, 0,
+      "Requestor",
+      \&_refresh,
+      \$hd{requestor},
+      @_requestors;
+    $_location = _createDropDown
+      $frame3,
+      0, 3,
+      "Location",
+      \&_refresh,
+      \$hd{location},
+      @_locations;
+
+    $_category = _createDropDown
+      $frame4,
+      0, 0,
+      "Category",
+      \&_refresh,
+      \$hd{category},
+      @_categories;
+    $_related_version = _createDropDown
+      $frame4,
+      0, 3,
+      "Related Version",
+      \&_refresh,
+      \$hd{related_version},
+      @_related_versions;
+
+    $_platform = _createDropDown
+      $frame5,
+      0, 0,
+      "Platform",
+      \&_refresh,
+      \$hd{platform},
+      @_platforms;
+    $_requestor_priority = _createDropDown
+      $frame5,
+      0, 3,
+      "Requested Priority",
+      \&_refresh,
+      \$hd{requestedpriority},
+      @_requested_priorities;
+
+    $_submit = _createButton $frame6, "Submit", \&_submit;
+
+    $_submit->configure (
+      -state   => "disabled",
+    );
+
+    _createButton $frame6, "Display",  \&_displayValues if (get_debug);
+    _createButton $frame6, "About",    \&_helpAbout;
+    _createButton $frame6, "Exit",     sub { _destroyHelpDeskUI };
+  } # createHelpDeskUI
+
+1;
diff --git a/cqtool/CreateWORUI.pm b/cqtool/CreateWORUI.pm
new file mode 100644 (file)
index 0000000..8a62a86
--- /dev/null
@@ -0,0 +1,575 @@
+##############################################################################
+#
+# Name: CreateWORUI.pm
+#
+# Description: CreateWORUI.pm is a Perl module that encapsulates a
+#             Perl/Tk application to create a WOR. This application
+#             was developed for a few reasons. First ucmwb needs to
+#             be able to create WORs. The approach was to use
+#             IBM/Rational's cqtool
+#             (/opt/rational/clearquest/bin/cqtool) but there is two
+#             problems with this. First IBM/Rational's cqtool is
+#             unsupported and documented. Secondly IBM/Rational's
+#             cqtool is going away as of Clearquest 7.0.
+#
+#             Another problem is that while IBM/Rational's cqtool
+#             would work, it does not return the ID of the WOR
+#             created!
+#
+#             So this Perl/Tk module was created to create WORs. Perl
+#             interfaces with Clearquest to call the appropraite
+#             Clearquest action hooks and the like. Note that only
+#             the basic information is asked for. If you really want
+#             to create or modify a full WOR use Clearquest. This
+#             Perl/Tk app's main customer is ucmwb.
+#
+# Author: Andrew@ClearSCM.com
+#
+# (c) Copyright 2007, General Dynamics, all rights reserved
+#
+##############################################################################
+use strict;
+use warnings;
+
+package CreateWORUI;
+  use Tk;
+  use Tk::Dialog;
+  use Tk::MyText;
+
+  use Display;
+  use CQTool;
+
+  use base "Exporter";
+
+  my $ME               = "CreateWOR";
+  my $VERSION          = "1.1";
+
+  # Colors
+  my ($EDIT_FOREGROUND, $EDIT_BACKGROUND);
+
+  our %wor;
+
+  our @EXPORT = qw (
+    createWORUI
+    %wor
+  );
+
+  # Globals
+  my $_createWORUI;
+
+  # Dropdowns
+  my (
+    $_projects,
+    $_rclcs,
+    $_prod_arch1s,
+    $_prod_arch2s,
+    $_engr_targets,
+    $_work_codes,
+    $_work_products,
+    $_wor_classes,
+  );
+
+  # Choice lists
+  my (
+    @_projects,
+    @_rclcs,
+    @_prod_arch1s,
+    @_prod_arch2s,
+    @_engr_targets,
+    @_work_codes,
+    @_work_products,
+    @_wor_classes,
+  );
+
+  # Buttons
+  my $_submit;
+
+  ############################################################################
+  # Subroutines
+  ############################################################################
+
+  #---------------------------------------------------------------------------
+  # _helpAbout (): Puts up the Help: About dialog box
+  #---------------------------------------------------------------------------
+  sub _helpAbout () {
+    my $text = "$ME v$VERSION\n";
+
+    $text .= <<END;
+
+This application creates a WOR using Perl/Tk. It is used by UCM/WB or can be used stand alone. It effectively replicates the functionality of Clearquest but 1) is blocking and 2) returns the RANCQ # so that UCM/WB can determine the number of the newly created WOR.
+
+Copyright General Dynamics © 2007 - All rights reserved
+Developed by Andrew DeFaria <Andrew\@ClearSCM.com> of ClearSCM, Inc.
+END
+
+    my $desc = $_createWORUI->Dialog (
+      -title           => "About $ME",
+      -text            => $text,
+      -buttons         => [ "OK" ],
+    );
+
+    $desc->Show ();
+  } # _helpAbout
+
+  #---------------------------------------------------------------------------
+  # _displayValues (): Displays the contents for %wor hash
+  #---------------------------------------------------------------------------
+  sub _displayValues () {
+    foreach (keys %wor) {
+      if ($wor{$_}) {
+        display ("$_: $wor{$_}");
+      } else {
+        display ("$_: undef");
+      } # if
+    } # foreach
+  } # _displayValues
+
+  #---------------------------------------------------------------------------
+  # _getChoices (): For a given $entity and $fieldname, this routine returns
+  #                the given choice list from Clearquest.
+  #---------------------------------------------------------------------------
+  sub _getChoices ($$) {
+    my ($entity, $fieldname) = @_;
+
+    return @{$entity->GetFieldChoiceList ($fieldname)};
+  } # _getChoices
+
+  #---------------------------------------------------------------------------
+  # _destroyCreateWORUI (): Destroys the current WOR UI recycling Tk objects
+  #---------------------------------------------------------------------------
+  sub _destroyCreateWORUI () {
+    # Destroy all globals created
+    destroy $_submit;
+    destroy $_projects;
+    destroy $_rclcs;
+    destroy $_prod_arch1s;
+    destroy $_prod_arch2s;
+    destroy $_engr_targets;
+    destroy $_work_codes;
+    destroy $_work_products;
+    destroy $_createWORUI;
+
+    $_submit           =
+    $_projects         =
+    $_rclcs            =
+    $_prod_arch1s      =
+    $_prod_arch2s      =
+    $_engr_targets     =
+    $_work_codes       =
+    $_work_products    =
+    $_wor_classes      =
+    $_createWORUI      = undef;
+
+    %wor = ();
+  } # _destroyCreateWORUI
+
+  #---------------------------------------------------------------------------
+  # _submit (): Actually creates the WOR given the filled out %wor hash.
+  #---------------------------------------------------------------------------
+  sub _submit () {
+    debug "Creating WOR...";
+    _displayValues if get_debug;
+    my $new_id = CQTool::submitWOR ($CQTool::entity, %wor);
+
+    display ($new_id) if $new_id;
+
+    _destroyCreateWORUI;
+
+    return $new_id;
+  } # _submit
+
+  #---------------------------------------------------------------------------
+  # _setSubmitButton (): Sets the submit button to active only if all required
+  #                     fields have values.
+  #---------------------------------------------------------------------------
+  sub _setSubmitButton (;$) {
+    my ($headline) = @_;
+
+    return if !$_submit;
+
+    # Check to see if we can activate the submit button
+    my $state = "normal";
+
+    foreach (@CQTool::wor_required_fields) {
+      if ($_ eq "headline") {
+        if (defined $headline) {
+         if ($headline eq "") {
+           $state = "disable";
+           last;
+         } else {
+           next;
+         } # if
+       } # if
+      } # if
+
+      if (!$wor{$_} or $wor{$_} eq "") {
+       $state = "disable";
+       last;
+      } # if
+    } # foreach
+
+    $_submit->configure (
+      -state   => $state,
+    );
+  } # _setSubmitButton
+
+  #---------------------------------------------------------------------------
+  # _validateText (): Gets the text from the MyText widget and sets the submit
+  #                  button
+  #---------------------------------------------------------------------------
+  sub _validateText {
+    my ($text) = @_;
+
+    $wor{description} = $text->get_text;
+    chomp $wor{description};
+
+    _setSubmitButton $text;
+
+    return 1;
+  } # _validateText
+
+  #---------------------------------------------------------------------------
+  # _validateEntry (): Gets the text from the headline widget and sets the
+  #                   submit button
+  #---------------------------------------------------------------------------
+  sub _validateEntry {
+    my ($entry) = @_;
+
+    _setSubmitButton $entry;
+
+    return 1;
+  } # _validateEntry
+
+  #---------------------------------------------------------------------------
+  # _createDropDown (): Creates a dropdown widget in $parent in a grid at the
+  #                    $x, $y coordinates with a $label and a $value, using
+  #                    dropdown @values and a $refresh procedure.
+  #---------------------------------------------------------------------------
+  sub _createDropDown ($$$$$$@) {
+    my ($parent, $x, $y, $label, $refresh, $value, @values) = @_;
+
+    $parent->Label (
+      -width           => length $label,
+      -text            => "$label:",
+    )->grid (
+      -row             => $x,
+      -column          => $y,
+      -sticky          => "e",
+    );
+
+    # Color the active foreground otherwise it's defaulted to ugly grey!
+    return $parent->Optionmenu (
+      -activeforeground        => $EDIT_FOREGROUND,
+      -activebackground        => $EDIT_BACKGROUND,
+      -command         => \&$refresh,
+      -variable                => $value,
+      -options         => \@values,
+    )->grid (
+      -row             => $x,
+      -column          => $y + 1,
+      -sticky          => "w",
+    );
+  } # _createDropDown
+
+  #---------------------------------------------------------------------------
+  # _createTextField (): Creates a text field widget in $parent with a $label
+  #                     and a $value, using a $maxlen and a $validate
+  #                     procedure.
+  #---------------------------------------------------------------------------
+  sub _createTextField ($$$$$) {
+    my ($parent, $label, $value, $maxlen, $validate) = @_;
+
+    $parent->Label (
+      -text            => "$label:",
+      -justify         => "right",
+      -width           => 10,
+    )->pack (
+      -side            => "left",
+      -anchor          => "e",
+    );
+
+    $parent->Entry (
+      -foreground      => $EDIT_FOREGROUND,
+      -background      => $EDIT_BACKGROUND,
+      -width           => $maxlen,
+      -justify         => "left",
+      -textvariable    => $value,
+      -validate                => "key",
+      -validatecommand => \&$validate,
+    )->pack (
+      -side            => "left",
+      -padx            => 5,
+      -anchor          => "e",
+    );
+  } # _createTextField
+
+  #---------------------------------------------------------------------------
+  # _createText (): Creates a multiline text field widget in $parent with a
+  #                $label and a $value, using the specified $rows and $cols
+  #                and a $validate procedure.
+  #---------------------------------------------------------------------------
+  sub _createText ($$$$$$) {
+    my ($parent, $label, $value, $rows, $cols, $validate) = @_;
+
+    $parent->Label (
+      -text            => "$label:",
+      -justify         => "right",
+      -width           => 10,
+    )->pack (
+      -side            => "left",+
+      -anchor          => "n",
+      -pady            => 5,
+    );
+
+    $parent->MyText (
+      -foreground      => $EDIT_FOREGROUND,
+      -background      => $EDIT_BACKGROUND,
+      -height          => $rows,
+      -width           => $cols,
+      -modified                => \&$validate,
+      -text            => $value,
+    )->pack (
+      -side            => "left",
+      -pady            => 5,
+      -anchor          => "s",
+    );
+  } # _createText
+
+  #---------------------------------------------------------------------------
+  # _createButton (): Creates a pushbutton widget in $parent with a $label and
+  #                  an $action.
+  #---------------------------------------------------------------------------
+  sub _createButton ($$$) {
+    my ($parent, $label, $action) = @_;
+
+    $parent->Button (
+      -activeforeground        => $EDIT_FOREGROUND,
+      -activebackground        => $EDIT_BACKGROUND,
+      -text            => $label,
+      -width           => length $label,
+      -command         => \$action
+    )->pack (
+      -side            => "left",
+      -padx            => 5
+    );
+  } # _createButton
+
+  #---------------------------------------------------------------------------
+  # _changeDropDown (): Refreshes the values in the dropdown menu.
+  #---------------------------------------------------------------------------
+  sub _changeDropDown ($@) {
+    my ($dropdown, @values) = @_;
+
+    if ($dropdown) {
+      my $menu = $dropdown->menu;
+
+      if ($menu) {
+       $dropdown->menu->delete (0, "end");
+      } # if
+
+      $dropdown->addOptions (@values);
+    } # if
+  } # _changeDropDown
+
+  #---------------------------------------------------------------------------
+  # _refresh (): Refreshes the application by getting news values from
+  #             Clearquest. Note a change in one dropdown may change others,
+  #             so we re-get all of them through this procedure.
+  #---------------------------------------------------------------------------
+  sub _refresh () {
+    my $fieldname;
+
+    $fieldname                 = "project";
+    my %projects       = CQTool::getProjects $CQTool::session;
+    $wor{$fieldname}   = $_projects[0] if !$wor{fieldname};
+    $CQTool::entity->SetFieldValue ($fieldname, $wor{$fieldname});
+
+    $fieldname         = "prod_arch1";
+    @_prod_arch1s      = _getChoices $CQTool::entity, $fieldname;
+    $wor{$fieldname}   = $_prod_arch1s[0] if !$wor{$fieldname};
+    $CQTool::entity->SetFieldValue ($fieldname, $wor{$fieldname});
+
+    $fieldname         = "prod_arch2";
+    @_prod_arch2s      = _getChoices $CQTool::entity, $fieldname;
+    $wor{$fieldname}   = $_prod_arch2s[0] if !$wor{$fieldname};
+    $CQTool::entity->SetFieldValue ($fieldname, $wor{$fieldname});
+
+    $fieldname         = "rclc_name";
+    @_rclcs            = @{$projects{$wor{project}}};
+    $wor{$fieldname}   = $_rclcs[0] if !$wor{$fieldname};
+    $CQTool::entity->SetFieldValue ($fieldname, $wor{$fieldname});
+
+    $fieldname         = "engr_target";
+    @_engr_targets     = _getChoices $CQTool::entity, $fieldname;
+    $wor{$fieldname}   = $_engr_targets[0] if !$wor{$fieldname};
+    $CQTool::entity->SetFieldValue ($fieldname, $wor{$fieldname});
+
+    $fieldname         = "work_code_name";
+    @_work_codes       = _getChoices $CQTool::entity, $fieldname;
+    $wor{$fieldname}   = $_work_codes[0] if !$wor{$fieldname};
+    $CQTool::entity->SetFieldValue ($fieldname, $wor{$fieldname});
+
+    $fieldname         = "work_product_name";
+    @_work_products    = _getChoices $CQTool::entity, $fieldname;
+    $wor{$fieldname}   = $_work_products[0] if !$wor{$fieldname};
+    $CQTool::entity->SetFieldValue ($fieldname, $wor{$fieldname});
+
+    _changeDropDown ($_projects,       keys %projects);
+    _changeDropDown ($_rclcs,          @_rclcs);
+    _changeDropDown ($_prod_arch1s,    @_prod_arch1s);
+    _changeDropDown ($_prod_arch2s,    @_prod_arch2s);
+    _changeDropDown ($_engr_targets,   @_engr_targets);
+    _changeDropDown ($_work_codes,     @_work_codes);
+    _changeDropDown ($_work_products,  @_work_products);
+
+    _setSubmitButton ();
+  } # _refresh
+
+  #---------------------------------------------------------------------------
+  # _darken (): Returns a slightly darker color than the passed in color
+  #---------------------------------------------------------------------------
+  sub _darken ($) {
+    my ($color) = @_;
+
+    # Get the RGB values
+    my ($r, $g, $b) = $_createWORUI->rgb($color);
+
+    # Set them to $DARKEN % of their previous values
+    my $DARKEN = .8;
+    my $rhex = sprintf "%x", $r * $DARKEN;
+    my $ghex = sprintf "%x", $g * $DARKEN;
+    my $bhex = sprintf "%x", $b * $DARKEN;
+
+    # Return a color string
+    return "\#$rhex$ghex$bhex";
+  } # _darken
+
+  #---------------------------------------------------------------------------
+  # createWORUI (): This is the main and exported routine that creates and
+  #                handles the entire Perl/Tk application for creating a
+  #                WOR.
+  #---------------------------------------------------------------------------
+  sub createWORUI () {
+    $_createWORUI = MainWindow->new;
+
+    $EDIT_FOREGROUND   = $_createWORUI->optionGet ("foreground", "Foreground");
+    $EDIT_BACKGROUND   = _darken ($_createWORUI->optionGet ("background", "Background"));
+
+    $wor{id} = "None" if !$wor{id};
+
+    $_createWORUI->title ("Submit WOR $wor{id}");
+
+    my $frame0 = $_createWORUI->Frame->pack (-pady => 2);
+    my $frame1 = $_createWORUI->Frame->pack;
+    my $frame2 = $_createWORUI->Frame->pack;
+    my $frame3 = $_createWORUI->Frame->pack;
+    my $frame4 = $_createWORUI->Frame->pack;
+
+    _createTextField (
+      $frame1,
+      "Headline",
+      \$wor{headline},
+      100,
+      \&_validateEntry
+    );
+
+    _createText (
+      $frame2,
+      "Description",
+      \$wor{description},
+      24, 100,
+      \&_validateText
+    );
+
+    my %projects = CQTool::getProjects ($CQTool::session);
+    @_projects = keys %projects;
+
+    $_projects = _createDropDown (
+      $frame3,
+      0, 0,
+      "Project",
+      \&_refresh,
+      \$wor{project},
+      @_projects
+    );
+    $_rclcs = _createDropDown (
+      $frame3,
+      0, 3,
+      "Revision Control Life Cycle",
+      \&_refresh,
+      \$wor{rclc_name},
+      @_rclcs
+    );
+
+    $_prod_arch1s = _createDropDown (
+      $frame3,
+      2, 0,
+      "Product Architecture 1",
+      \&_refresh,
+      \$wor{prod_arch1},
+      @_prod_arch1s
+    );
+    $_engr_targets = _createDropDown (
+      $frame3,
+      2, 3,
+      "Engineering Target",
+      \&_refresh,
+      \$wor{engr_target},
+      @_engr_targets
+    );
+
+    $_prod_arch2s = _createDropDown (
+      $frame3,
+      4, 0,
+      "Product Architecture 2",
+      \&_refresh,
+      \$wor{prod_arch2},
+      @_prod_arch2s
+    );
+    $_work_codes = _createDropDown (
+      $frame3,
+      4, 3,
+      "Work Code",
+      \&_refresh,
+      \$wor{work_code_name},
+      @_work_codes
+    );
+
+    $_work_products = _createDropDown (
+      $frame3,
+      6, 0,
+      "Work Product",
+      \&_refresh,
+      \$wor{work_product_name},
+      @_work_products
+    );
+
+    my $fieldname      = "wor_class";
+    @_wor_classes      = _getChoices $CQTool::entity, $fieldname;
+    $wor{$fieldname}   = "Worker";
+    $CQTool::entity->SetFieldValue ($fieldname, $wor{$fieldname});
+
+    $_wor_classes = _createDropDown (
+      $frame3,
+      6, 3,
+      "WOR Class",
+      sub {},
+      \$wor{wor_class},
+      @_wor_classes
+    );
+
+    # Default WOR Class to Worker
+    $_wor_classes->setOption ("Worker");
+
+    $_submit = _createButton ($frame4, "Submit", \&_submit);
+
+    $_submit->configure (
+      -state   => "disabled",
+    );
+
+    _createButton ($frame4, "Display", \&_displayValues) if (get_debug);
+    _createButton ($frame4, "About",   \&_helpAbout);
+    _createButton ($frame4, "Exit",    \&_destroyCreateWORUI);
+  } # createWORUI
+
+1;
diff --git a/cqtool/cqtool.pl b/cqtool/cqtool.pl
new file mode 100755 (executable)
index 0000000..3c85914
--- /dev/null
@@ -0,0 +1,780 @@
+#!/usr/bin/env /opt/rational/clearquest/bin/cqperl\r
+##############################################################################\r
+#\r
+# Name: cqtool\r
+#\r
+# Description: cqtool is an interface to Clearquest to perform some simple\r
+#              actions to the RANCQ database. It is used primarily by ucmwb\r
+#              but it also supports a command line interface.\r
+#\r
+#              The following commands are supported:\r
+#\r
+#              activate <wor> <project> <est_hours> <startdate> <enddate>:\r
+#                      Activate WOR\r
+#              assign <wor> <assignee> <project> <planned_hours> <startdate>:\r
+#                      Assign the WOR\r
+#              clone <wor>:\r
+#                      Clones a WOR\r
+#              comment <wor> <comment>\r
+#                      Add a comment to the Notes_Entry field for the WOR\r
+#              complete <wor> <actual_hours>:\r
+#                      Complete WOR\r
+#              createhd:\r
+#                      Create a new Help Desk Ticket\r
+#              createwor:\r
+#                      Create a new WOR\r
+#              effort <wor> <hours>:\r
+#                      Update the WOR's actual hours\r
+#              exit|quit:\r
+#                      Exits cqtool\r
+#              help:\r
+#                      This display\r
+#              link <parent wor> <child wor>:\r
+#                      Link a parent WOR to a child WOR\r
+#              resolve <wor>:\r
+#                      Resolve WOR\r
+#              set <wor> <field> <value>\r
+#                      Set <field> to <value> for the <wor>\r
+#              usage:\r
+#                      Displays command line usage\r
+#              version:\r
+#                      Displays version of cqtool\r
+#\r
+#              Many of these commands simply perform actions on a wor. Two\r
+#              of these commands, createwor and createhd have Perl/Tk GUI\r
+#              interfaces.\r
+#\r
+# Command line usage:\r
+#\r
+# Usage: cqtool\t[-usage|help] [-verbose] [-debug]\r
+#      [-userid <user>] [-password <password>] [<command>]\r
+#\r
+# Where:\r
+#\r
+#   -usage|help:       Display usage\r
+#   -verbose:          Turn on verbose mode\r
+#   -debug:            Turn on debug mode\r
+#   -userid:           User ID to log into Clearquest database as\r
+#   -password:         Password to use\r
+#   <command>          If specified then cqtool executes <command> and\r
+#                      exits\r
+#\r
+# Environment:         cqtool supports the following environment variables\r
+#                      that are used mostly for tesing purposes\r
+#\r
+#      CQ_DBSET:       Clearquest DBSET to open (e.g. XTST3 for testing -\r
+#                      default RANCQ)  \r
+#      CQ_USER:        User name to log into the $CQ_DBSET database with\r
+#      CQ_PASSWORD:    Password to use to log into the $CQ_DBSET with.\r
+#\r
+# Author: Andrew@DeFaria.com\r
+#\r
+# (c) Copyright 2007, General Dynamics, all rights reserved\r
+#\r
+##############################################################################\r
+use strict;\r
+use warnings;\r
+\r
+use CQPerlExt;\r
+use FindBin;\r
+use Getopt::Long;\r
+use Term::ANSIColor qw (:constants);\r
+\r
+use lib ("$FindBin::Bin", "$FindBin::Bin/../lib");\r
+\r
+use SCCM::Misc;\r
+use Display;\r
+use CQTool;\r
+use CreateWORUI;\r
+use CreateHelpDeskUI;\r
+use Logger;\r
+\r
+my $VERSION            = BOLD GREEN . "1.1" . RESET;\r
+my $PROMPT             = BOLD YELLOW . ">>" . RESET;\r
+my $UCMWB_PROMPT       = ">>";\r
+my $DESC               = BOLD RED . "$FindBin::Script" .\r
+                         RESET      " Version " .\r
+                         $VERSION .\r
+                         CYAN ": Program to talk to Clearquest" .\r
+                         RESET;\r
+\r
+# Globals\r
+my $_userid    = $ENV{CQ_USER}  ? $ENV{CQ_USER} : $ENV{USER};\r
+my $_password  = $ENV{CQ_PASSWORD};\r
+my $_db_name   = $ENV{CQ_DBSET} ? $ENV{CQ_DBSET} : "RANCQ";\r
+my $_ucmwb;\r
+\r
+my $_log;\r
+\r
+if (get_debug) {\r
+  $_log = new Logger (\r
+    path => "/tmp",\r
+    append => 1,\r
+  );\r
+} # if\r
+\r
+my %_commands = (\r
+  activate     => \&activate,\r
+  assign       => \&assign,\r
+  clone                => \&clone,\r
+  comment      => \&comment,\r
+  complete     => \&complete,\r
+  createhd     => \&createHelpDesk,\r
+  createwor    => \&createWOR,\r
+  effort       => \&effort,\r
+  exit         => \&shutdown,\r
+  help         => \&help,\r
+  link         => \&linkParentWor2ChildWor,\r
+  quit         => \&shutdown,\r
+  resolve      => \&resolve,\r
+  set          => \&set,\r
+  usage                => \&usage,\r
+  version      => \&announce,\r
+);\r
+\r
+##############################################################################\r
+# Forwards\r
+##############################################################################\r
+sub commandLoop (@);\r
+\r
+##############################################################################\r
+# Main\r
+##############################################################################\r
+MAIN: {\r
+  GetOptions (\r
+    "usage"            => sub { usage () },\r
+    "verbose"          => sub { set_verbose () },\r
+    "debug"            => sub { set_debug () },\r
+    "userid=s"         => \$_userid,\r
+    "password=s"       => \$_password,\r
+    "database=s"       => \$_db_name,\r
+    "ucmwb"            => \$_ucmwb,\r
+  ) || usage ();\r
+\r
+  exit (commandLoop(@ARGV));\r
+} # MAIN\r
+\r
+##############################################################################\r
+# Subroutines\r
+##############################################################################\r
+\r
+#-----------------------------------------------------------------------------\r
+# shutdown (): Ends program\r
+#-----------------------------------------------------------------------------\r
+sub shutdown () {\r
+  exit (0);\r
+} # exit\r
+\r
+#-----------------------------------------------------------------------------\r
+# help (): Displays help\r
+#-----------------------------------------------------------------------------\r
+sub help () {\r
+  display ($DESC);\r
+  display <<END;\r
+\r
+Valid commands are:\r
+\r
+activate <wor> <project> <est_hours> <startdate> <enddate>:\r
+       Activate WOR\r
+assign <wor> <assignee> <project> <planned_hours> <startdate>:\r
+       Assign the WOR\r
+clone <wor>:\r
+       Clones a WOR\r
+comment <wor> <comment>\r
+       Add a comment to the Notes_Entry field for the WOR\r
+complete <wor> <actual_hours>:\r
+       Complete WOR\r
+createhd:\r
+       Create a new Help Desk Ticket\r
+createwor:\r
+       Create a new WOR\r
+effort <wor> <hours>:\r
+       Update the WOR's actual hours\r
+exit|quit:\r
+       Exits $FindBin::Script\r
+help:\r
+       This display\r
+link <parent wor> <child wor>:\r
+       Link a parent WOR to a child WOR\r
+resolve <wor>:\r
+       Resolve WOR\r
+set <wor> <field> <value>\r
+       Set <field> to <value> for the <wor>\r
+usage:\r
+       Displays command line usage\r
+version:\r
+       Displays version of $FindBin::Script\r
+END\r
+} # help\r
+\r
+#-----------------------------------------------------------------------------\r
+# announce (): Announce ourselves\r
+#-----------------------------------------------------------------------------\r
+sub announce () {\r
+  display ($DESC);\r
+} # Announce\r
+\r
+#-----------------------------------------------------------------------------\r
+# dberror ($): Handle errors when talking to Clearquest. Note we need to reset\r
+#             the database connection if an error happens.\r
+#-----------------------------------------------------------------------------\r
+sub dberror ($) {\r
+  my ($msg) = @_;\r
+\r
+  # Need to not only report the error but to reopen the\r
+  # database. Something gets corruppted if we don't!\r
+  error ($msg);\r
+\r
+  closeDB ();\r
+\r
+  openDB ($_userid, $_password, $_db_name);\r
+} # DBError\r
+\r
+#-----------------------------------------------------------------------------\r
+# getEntity ($$): Get an entity from Clearquest\r
+#-----------------------------------------------------------------------------\r
+sub getEntity ($$) {\r
+  my ($recordname, $wor) = @_;\r
+\r
+  my $entity;\r
+\r
+  eval {\r
+    $entity = $CQTool::session->GetEntity ($recordname, $wor);\r
+  };\r
+\r
+  if ($@) {\r
+    chomp $@;\r
+    dberror ($@);\r
+    return undef;\r
+  } else {\r
+    return $entity;\r
+  } # if\r
+} # getEntity\r
+\r
+#-----------------------------------------------------------------------------\r
+# set ($$$): Set $field to $value for $wor\r
+#-----------------------------------------------------------------------------\r
+sub set ($$@) {\r
+  my ($wor, $field, $value) = @_;\r
+\r
+  if (!$wor or $wor eq "") {\r
+    error ("WOR is required");\r
+    return 1;\r
+  } # if\r
+\r
+  if (!$field or $field eq "") {\r
+    error ("Field is required");\r
+    return 1;\r
+  } # if\r
+\r
+  my $entity   = getEntity ("WOR", $wor);\r
+\r
+  return 1 if !$entity;\r
+\r
+  $session->EditEntity ($entity, "modify");\r
+\r
+  $_log->msg ("Modifying $field to \"$value\"") if get_debug;\r
+  eval {\r
+    $entity->SetFieldValue ($field, $value);\r
+  };\r
+\r
+  if ($@) {\r
+    dberror ("$field set failed for WOR $wor:\n$@");\r
+    return 2;\r
+  } # if\r
+\r
+  my $status = $entity->Validate ();\r
+\r
+  if ($status ne "") {\r
+    $entity->Revert ();\r
+    error ("$field validate failed for WOR $wor:\n$status");\r
+    return 2;\r
+  } # if\r
+\r
+  $status = $entity->Commit ();\r
+\r
+  if ($status ne "") {\r
+    error ("$field update failed during Submit for $wor:\n$status");\r
+    return 2;\r
+  } # if\r
+\r
+   return 0;\r
+} # set\r
+\r
+#-----------------------------------------------------------------------------\r
+# clone ($): Clone a WOR\r
+#-----------------------------------------------------------------------------\r
+sub clone ($) {\r
+  my ($wor) = @_;\r
+\r
+  if (!$wor) {\r
+    error ("WOR not specified!");\r
+    return 1;\r
+  } # if\r
+\r
+  $entity = getEntity ("WOR", $wor);\r
+\r
+  return 1 if !$entity;\r
+\r
+  # Check state\r
+  my $state = $entity->GetFieldValue ("state")->GetValue ();\r
+\r
+  if ($state ne "Closed") {\r
+    error ("WOR $wor not closed - Unable to clone!");\r
+    return 1;\r
+  } # if\r
+\r
+  verbose ("Cloning WOR $wor...");\r
+\r
+  my $result = 0;\r
+\r
+  eval {\r
+    # Currently Clone doesn't return a proper result but eventually...\r
+    $result = $CQTool::session->FireRecordScriptAlias ($entity, "Clone");\r
+  };\r
+\r
+  if ($@) {\r
+    chomp $@;\r
+    dberror ($@);\r
+    return 1;\r
+  } # if\r
+\r
+  return $result;\r
+} # clone\r
+\r
+#-----------------------------------------------------------------------------\r
+# effort ($$): Update actual hours for a WOR\r
+#-----------------------------------------------------------------------------\r
+sub effort ($$) {\r
+  my ($wor, $actualHrs) = @_;\r
+\r
+  return set $wor, "ActualEffort", $actualHrs;\r
+} # effort\r
+\r
+#-----------------------------------------------------------------------------\r
+# comment (): Update the Notes_Entry comment field for a WOR\r
+#-----------------------------------------------------------------------------\r
+sub comment ($) {\r
+  my ($wor) = @_;\r
+\r
+  if (!$wor) {\r
+    error "WOR not defined in call to comment!";\r
+    return 1;\r
+  } # if\r
+\r
+  if (!$_ucmwb) {\r
+    display ("Enter comments below. When finished, enter \".\" on a line by itself or hit ^D:");\r
+  } else {\r
+    # We still need to prompt for the comments however signal UCMWB\r
+    # that command is ready for more input.\r
+    display_nolf ($UCMWB_PROMPT);\r
+  } # if\r
+\r
+  my $comments;\r
+\r
+  while (<STDIN>) {\r
+    last if $_ eq ".\n";\r
+    $comments .= $_;\r
+  } # while\r
+\r
+  chomp $comments;\r
+\r
+  $_log->msg ("Comments:\n$comments") if get_debug;\r
+\r
+  return set $wor, "Note_Entry", $comments;\r
+} # Comment\r
+\r
+#-----------------------------------------------------------------------------\r
+# linkParentWor2ChildWor ($$): Link a child WOR to a parent WOR\r
+#-----------------------------------------------------------------------------\r
+sub linkParentWor2ChildWor ($$) {\r
+  my ($parentWor, $childWor) = @_;\r
+\r
+  my $status;\r
+\r
+  verbose ("Linking $parentWor -> $childWor...");\r
+\r
+  my $childentity      = getEntity ("WOR", $childWor);\r
+  my $parententity     = getEntity ("WOR", $parentWor);\r
+\r
+  return 1 unless $childentity and $parententity;\r
+\r
+  $session->EditEntity ($parententity, "modify");\r
+\r
+  $parententity->AddFieldValue ("wor_children", $childWor);\r
+\r
+  $status = $parententity->Validate ();\r
+\r
+  if ($status ne "") {\r
+    $parententity->Revert ();\r
+    error ("Validation failed while attempting to add child WOR $childWor to parent WOR $parentWor:\n$status");\r
+    return 1;\r
+  } # if\r
+\r
+  eval {\r
+    $status = $parententity->Commit ();\r
+  };\r
+\r
+  $status = $@ if $@;\r
+\r
+  if ($status ne "") {\r
+    (error "Commit failed while trying to add child WOR $childWor to parent WOR $parentWor:\n$status");\r
+    return 2;\r
+  } # if\r
+\r
+  debug "Modifying child $childWor...";\r
+  $session->EditEntity ($childentity, "modify");\r
+\r
+  $childentity->SetFieldValue ("wor_parent", $parentWor);\r
+\r
+  $status = $childentity->Validate ();\r
+\r
+  if ($status ne "") {\r
+    $childentity->Revert ();\r
+    error "Validation failed while attempting to add parent WOR $parentWor to child WOR $childWor:\n$status";\r
+    return 1;\r
+  } # if\r
+\r
+  eval {\r
+    $status = $childentity->Commit ();\r
+  };\r
+\r
+  $status = $@ if $@;\r
+\r
+  if ($status ne "") {\r
+    error "Commit failed while trying to add parent WOR $parentWor to child WOR $childWor:\n$status";\r
+    return 2;\r
+  } # if\r
+\r
+  return 0;\r
+} # linkParentWor2ChildWor\r
+\r
+#-----------------------------------------------------------------------------\r
+# assign ($$$$): Assign a WOR\r
+#-----------------------------------------------------------------------------\r
+sub assign ($$$$$) {\r
+  my ($wor, $assignee, $project, $plannedHrs, $startDate) = @_;\r
+\r
+  if (!$wor or $wor eq "") {\r
+    error ("WOR is required");\r
+    return 1;\r
+  } # if\r
+\r
+  if (!$assignee or $assignee eq "") {\r
+    error ("Assignee must be specified");\r
+    return 1;\r
+  } # if\r
+\r
+  if (!$project or $project eq "") {\r
+    error ("UCM Project is required");\r
+    return 1;\r
+  } # if\r
+\r
+  if (!$startDate or $startDate eq "") {\r
+    error ("Planned Start Date is required");\r
+    return 1;\r
+  } # if\r
+\r
+  my $entity   = getEntity ("WOR", $wor);\r
+\r
+  return 1 if !$entity;\r
+\r
+  my $state    = $entity->GetFieldValue ("state")->GetValue ();\r
+\r
+  if ($state ne "Submitted") {\r
+    error ("WOR $wor is not in Submitted state!\nState: $state");\r
+    return 2;\r
+  } # if\r
+\r
+  $session->EditEntity ($entity, "assign");\r
+\r
+  $entity->SetFieldValue ("ucm_project",       $project)       if $project     ne "";\r
+  $entity->SetFieldValue ("PlannedStart",      $startDate)     if $startDate   ne "";\r
+  $entity->SetFieldValue ("PlannedEffort",     $plannedHrs)    if $plannedHrs  ne "";\r
+  $entity->SetFieldValue ("Owner",             $assignee)      if $assignee    ne "";\r
+\r
+  my $status = $entity->Validate ();\r
+\r
+  if ($status ne "") {\r
+    $entity->Revert ();\r
+    error ("Assign failed for WOR $wor:\n$status");\r
+    return 2;\r
+  } # if\r
+\r
+  $status = $entity->Commit ();\r
+\r
+  if ($status ne "") {\r
+    error ("Assign failed during Submit for WOR $wor:\n$status");\r
+    return 2;\r
+  } # if\r
+\r
+  return 0;\r
+} # assign\r
+\r
+#-----------------------------------------------------------------------------\r
+# activate (): Activate a WOR\r
+#-----------------------------------------------------------------------------\r
+sub activate ($$$$$) {\r
+  my ($wor, $project, $estHrs, $startDate, $endDate) = @_;\r
+\r
+  if (!$wor or $wor eq "") {\r
+    error ("WOR is required");\r
+    return 1;\r
+  } # if\r
+\r
+  if (!$project or $project eq "") {\r
+    error ("UCM Project is required");\r
+    return 1;\r
+  } # if\r
+\r
+  if (!$startDate or $startDate eq "") {\r
+    error ("Planned Start Date is required");\r
+    return 1;\r
+  } # if\r
+\r
+  if (!$endDate or $endDate eq "") {\r
+    error ("Planned End Date is required");\r
+    return 1;\r
+  } # if\r
+\r
+  my $entity   = getEntity ("WOR", $wor);\r
+\r
+  return 1 if !$entity;\r
+\r
+  my $state    = $entity->GetFieldValue ("state")->GetValue ();\r
+\r
+  if ($state ne "Assessing") {\r
+    error ("WOR $wor is not in Assessing state!\nstate: $state");\r
+    return 2;\r
+  } # if\r
+\r
+  $session->EditEntity ($entity, "activate");\r
+\r
+  $entity->SetFieldValue ("ucm_project",       $project)       if $project ne "";\r
+  $entity->SetFieldValue ("EstimatedEffort",   $estHrs)        if $estHrs ne "";\r
+  $entity->SetFieldValue ("PlannedStart",      $startDate)     if $startDate ne "";\r
+  $entity->SetFieldValue ("PlannedEnd",                $endDate)       if $endDate ne "";\r
+\r
+  my $status = $entity->Validate ();\r
+\r
+  if ($status ne "") {\r
+    $entity->Revert ();\r
+    error ("Activate failed for WOR $wor:\n$status");\r
+    return 2;\r
+  } # if\r
+\r
+  $status = $entity->Commit ();\r
+\r
+  if ($status ne "") {\r
+    error ("Activate failed during Submit for WOR $wor:\n$status");\r
+    return 2;\r
+  } # if\r
+\r
+   return 0;\r
+} # activate\r
+\r
+#-----------------------------------------------------------------------------\r
+# resolve ($): Resolve a WOR\r
+#-----------------------------------------------------------------------------\r
+sub resolve ($) {\r
+  my ($wor) = @_;\r
+\r
+  if (!$wor or $wor eq "") {\r
+    error ("WOR is required");\r
+    return 1;\r
+  } # if\r
+\r
+  my $entity   = getEntity ("WOR", $wor);\r
+\r
+  return 1 if !$entity;\r
+\r
+  my $state    = $entity->GetFieldValue ("state")->GetValue ();\r
+\r
+  if ($state ne "Working") {\r
+    error ("WOR $wor is not in Working state!\nState: $state");\r
+    return 2;\r
+  } # if\r
+\r
+  $session->EditEntity ($entity, "resolve");\r
+\r
+  my $status = $entity->Validate ();\r
+\r
+  if ($status ne "") {\r
+    $entity->Revert ();\r
+    error ("Resolve failed for WOR $wor:\n$status");\r
+    return 2;\r
+  } # if\r
+\r
+  $status = $entity->Commit ();\r
+\r
+  if ($status ne "") {\r
+    error ("Resolve failed during Submit for WOR $wor:\n$status");\r
+    return 2;\r
+  } # if\r
+\r
+   return 0;\r
+} # resolve\r
+\r
+#-----------------------------------------------------------------------------\r
+# complete ($$): Complete a WOR\r
+#-----------------------------------------------------------------------------\r
+sub complete ($$) {\r
+  my ($wor, $actualHrs) = @_;\r
+\r
+  if (!$wor or $wor eq "") {\r
+    error ("WOR is required");\r
+    return 1;\r
+  } # if\r
+\r
+  if (!$wor or $wor eq "") {\r
+    error ("Actual Hours are required");\r
+    return 1;\r
+  } # if\r
+\r
+  my $entity   = getEntity ("WOR", $wor);\r
+\r
+  return 1 if !$entity;\r
+\r
+  my $state    = $entity->GetFieldValue ("state")->GetValue ();\r
+\r
+  if ($state ne "Verifying") {\r
+    error ("WOR $wor is not in Verifying state!\nState:$state");\r
+    return 2;\r
+  } # if\r
+\r
+  $session->EditEntity ($entity, "complete");\r
+  $entity->SetFieldValue ("ActualEffort", $actualHrs) if $actualHrs ne "";\r
+\r
+  my $status = $entity->Validate ();\r
+\r
+  if ($status ne "") {\r
+    $entity->Revert ();\r
+    error ("Complete failed for WOR $wor:\n$status");\r
+    return 2;\r
+  } # if\r
+\r
+  $status = $entity->Commit ();\r
+\r
+  if ($status ne "") {\r
+    error ("Complete failed during Submit for WOR $wor:\n$status");\r
+    return 2;\r
+  } # if\r
+\r
+   return 0;\r
+} # Complete\r
+\r
+#-----------------------------------------------------------------------------\r
+# executeCommand (@): Executes a cqtool command\r
+#-----------------------------------------------------------------------------\r
+sub executeCommand (@) {\r
+  my (@args) = @_;\r
+\r
+  my $cmd = lc shift @args;\r
+\r
+  return if $cmd eq "";\r
+\r
+  if ($_commands{$cmd}) {\r
+    if (!$CQTool::session) {\r
+      if ( # Commands that do not require a database connection\r
+         !($cmd eq "exit"      or\r
+           $cmd eq "quit"      or\r
+           $cmd eq "help"      or\r
+           $cmd eq "usage"     or\r
+           $cmd eq "verbose")) {\r
+       verbose "Opening $_db_name as $_userid...";\r
+\r
+       if (!$_password) {\r
+         display_nolf ("${_userid}'s password:");\r
+         `stty -echo`;\r
+         $_password = <STDIN>;\r
+         chomp $_password;\r
+         display ("");\r
+         `stty echo`;\r
+       } # if\r
+\r
+       openDB ($_userid, $_password, $_db_name);\r
+      } # if\r
+    } # if\r
+\r
+    # Treat args: Args that are enclosed in quotes must be\r
+    # combined. For simplicity's sake we will only support matched\r
+    # pairs of double quotes. Anything else results in undefined\r
+    # behavior.\r
+    my (@new_args);\r
+\r
+    foreach (@args) {\r
+      # Quoted argument starting\r
+      if (/^\"(.*)\"$/s) {\r
+       push @new_args, $1;\r
+      } else {\r
+       push @new_args, $_;\r
+      } # if\r
+    } # foreach\r
+\r
+    $_log->msg ("$cmd (" . join (",", @new_args) . ")") if get_debug;\r
+\r
+    return $_commands{$cmd} (@new_args);\r
+  } else {\r
+    error ("Unknown command \"$cmd\" (try help)");\r
+    return 1;\r
+  } # if\r
+} # executeCommand\r
+\r
+#-----------------------------------------------------------------------------\r
+# commandLoop (@): This is the interactive command loop\r
+#-----------------------------------------------------------------------------\r
+sub commandLoop (@) {\r
+  my (@args) = @_;\r
+\r
+  # For single, command line, commands...\r
+  return executeCommand (@args) if @args;\r
+\r
+  announce if !$_ucmwb;\r
+\r
+  while () {\r
+    if (!$_ucmwb) {\r
+      display_nolf ($PROMPT . RESET . UNDERLINE);\r
+    } else {\r
+      display_nolf ($UCMWB_PROMPT);\r
+    } # if\r
+\r
+    # Read command into $_\r
+    $_ = <STDIN>;\r
+    chomp;\r
+\r
+    # If we are not being called by ucmwb, display RESET to stop the\r
+    # UNDERLINE we were using. This keeps the output from being\r
+    # underlined. In ucmwb mode we are not using any of the terminal\r
+    # sequences.\r
+    display_nolf (RESET) if !$_ucmwb;\r
+\r
+    # If the user hit Control-d then a ^D is displayed but we remain\r
+    # on the same line. So output a carriage return and exit 0.\r
+    if (!$_) {\r
+      display ("");\r
+      exit 0;\r
+    } # if\r
+\r
+    # Special handling for set command since we want to take\r
+    # everything after <field> to be a value, and we may get long\r
+    # values that are space separated and space significant\r
+    # (e.g. description?)\r
+    if (/^\s*(\w+)\s+(\w+)\s+(\w+)\s+(.*)/) {\r
+      if (lc $1 eq "set") {\r
+       my $cmd         = $1;\r
+       my $wor         = $2;\r
+       my $field       = $3;\r
+       my $value       = $4;\r
+\r
+       # Change "\n"'s back to \n's\r
+       $value =~ s/\\n/\n/g;\r
+\r
+       executeCommand ($cmd, $wor, $field, "\"$value\"");\r
+      } else {\r
+       executeCommand (split);\r
+      } # if\r
+    } else {\r
+      executeCommand (split);\r
+    } # if\r
+  } # while\r
+} # commandLoop\r
diff --git a/rantest/East.pm b/rantest/East.pm
new file mode 100644 (file)
index 0000000..d03a84e
--- /dev/null
@@ -0,0 +1,2991 @@
+#############################################################################
+#
+# Name:                East.pm
+#
+# Description: East.pm is a Perl module that encapsulates the East Simulator
+#              as an object. Methods are provided to connect, configure and
+#              run tests on an East Simulator.
+#
+# Author:      Andrew@DeFaria.com
+#
+# Copyright (c) 2008 General Dynamics
+#
+# All rights reserved except as subject to DFARS 252.227-7014 of contract
+# number CP02H8901N issued under prime contract N00039-04-C-2009.
+#
+# Warning: This document contains technical data whose export is restricted
+# by the Arms Export Control Act (Title 22, U.S.C., Sec 2751, et seq.) or the
+# Export Administration Act of 1979, as amended, Title, 50, U.S.C., App. 2401
+# et seq. Violations of these export laws are subject to severe criminal
+# penalties. Disseminate in accordance with provisions of DoD Directive
+# 5230.25.
+#
+##############################################################################
+use strict;
+use warnings;
+
+package Nethawk::East;
+
+use Carp;
+use Expect;
+use File::Basename;
+use File::Copy;
+use File::Path;
+use File::Temp qw (tempfile);
+use Getopt::Long;
+
+use DateUtils;
+use Display;
+use Utils;
+use Rexec;
+use SCCM::Build::Utils;
+
+use constant DEFAULT_TIMEOUT   => 180;
+use constant CCMACHINE         => "cclinux";
+use constant CLEARTOOL         => "ssh " . CCMACHINE . " \"cd $ENV{PWD} && /opt/rational/clearcase/bin/cleartool\"";
+
+use constant RANHOST           => "ranray";
+use constant RANUSER           => "pswit";
+
+use constant LOGHOST           => "seast1";
+use constant LOGUSER           => "pswit";
+use constant LOGBASE           => "$ENV{MNT_DIR}/testlogs";
+use constant RANTVL_LOGBASE    => "/export/rantvl";
+
+# This is a non-standard, but commonly used prompt around here. For
+# EAST systems they use a terminator of "]$" as in "[p6258c@ceast1
+# p6258c]$ " however on ranray it's more like "[ranray/home/pwit]
+# ". So we look for both.
+use constant PROMPT            => qr'(\]\$|\] $)';
+
+############################################################################
+# Globals
+############################################################################
+my %_validTestTypes = (
+  "load"       => "LoadTCRunner",
+  "manual"     => "Manual",
+  "pool"       => "RegressionLoadRunner",
+  "tc"         => "RegressionRunner",
+  "ts"         => "RegressionTSRunner",
+  "log"                => "Rantvl",
+  "shell"      => "Shell",
+);
+
+sub LogDebug ($) {
+  my ($msg) = @_;
+
+  open FILE, ">>/tmp/rantest.debug.log"
+    or die "Unable to open /tmp/rantest.debug.log for append - $!";
+
+  print FILE "$msg";
+
+  close FILE;
+} # LogDebug
+
+############################################################################
+#
+# new: Instantiate a new East object
+#
+# Parms:
+#   none
+#
+# Returns:     New East object
+#
+############################################################################
+sub new {
+  my ($class) = @_;
+
+  bless {
+    timeout    => DEFAULT_TIMEOUT,
+    prompt     => PROMPT,
+  }, $class;
+} # new
+
+############################################################################
+#
+# validTestType:       Return a status indicating if the passed in
+#                      test type is valid (and an error message if not)
+# Parms:
+#   testType:          Type of test requested
+#
+# Returns:             List contains a status (0 = valid test type, 1 =
+#                      invalid test type) and an optional error message.
+#
+############################################################################
+sub validTestType ($) {
+  my ($testType) = @_;
+
+  $testType = "<undefined>" if !$testType;
+
+  return (0, "") if InArray (lc $testType, keys %_validTestTypes);
+
+  my $msg = "Type must be one of:\n\n";
+
+  foreach (sort keys %_validTestTypes) {
+    $msg .= "  $_\t$_validTestTypes{$_}\n";
+  } # foreach
+
+  return (1, $msg);
+} # validTestType
+
+############################################################################
+#
+# inUse:       Check if the unit type and number is in use. Returns undef
+#              if it is not being used or an error message if it is.
+# Parms:       none
+#
+# Returns:     List contains a status (0 = not in use, 1 = in use) and an
+#              optional error message.
+#
+############################################################################
+sub inUse ($$) {
+  my ($self) = @_;
+
+  my $dut = "$self->{unitType}$self->{unitNbr}";
+
+  my $lockfile1 = "$ENV{MNT_DIR}/$ENV{EAST_REL}/DUT/$dut/desktop.lock";
+  my $lockfile2 = "$ENV{MNT_DIR}/$ENV{EAST_REL}/loadservers/$dut/desktop.lock";
+
+  my ($owner, @lines);
+
+  if (-f $lockfile1) {
+    @lines = `ls -l $lockfile1`;
+
+    $owner = (split /\s+/, $lines[0])[2] if $lines[0];
+  } elsif (-f $lockfile2) {
+    @lines = `ls -l $lockfile2`;
+
+    $owner = (split /\s+/, $lines[0])[2] if $lines[0];
+  } else {
+    return undef;
+  } # if
+
+  my $owner_name = "Unknown user";
+
+  return "ERROR: $dut is being tested now by $owner_name.\nDo not attempt to start EAST, it could cause serious problems." if !$owner;
+
+  @lines = `ypmatch $owner passwd 2>&1`;
+
+  if ($? == 0) {
+    $owner_name = (split /:/, $lines[0])[4];
+  } else {
+    $owner_name = "ypmatch $owner passwd - failed";
+  } # if
+
+  if ($ENV{LOGNAME} eq $owner) {
+    return "East in use by you. Exit east using desktop button before starting again.";
+  } else {
+    return "$dut is being tested now by $owner_name.\nDo not attempt to start EAST, it could cause serious problems.";
+  } # if
+} # inUse
+
+############################################################################
+#
+# viewExists:  Checks to see if a remote view exists.
+#
+# Parms:
+#   tag:       View tag to check
+#
+# Returns:     List contains a status (0 = view does not exist, 1 = view
+#              exists) and the optional output from the lsview command.
+#
+############################################################################
+sub viewExists ($) {
+  my ($self, $tag) = @_;
+
+  my $cmd = CLEARTOOL . " lsview $tag 2>&1";
+  my @lines = `$cmd`;
+
+  return ($?, @lines);
+} # viewExists
+
+############################################################################
+#
+# testExists:  Checks to see if a test exists
+#
+# Parms:
+#   type:      Type of test to check (rbs, rnc or east)
+#   name:      Name of test
+#
+# Returns:     0 if test exists, 1 if it doesn't.
+#
+############################################################################
+sub testExists ($$) {
+  my ($self, $type, $name) = @_;
+
+  return 1 unless $self->{view};
+
+  return 1 if $name eq "";
+
+  my $vobPath = "vobs/simdev/tc_data";
+
+  # Now compose testPath
+  my $testPath = "$ENV{MNT_DIR}/snapshot_views/$self->{userdir}/$self->{view}/$vobPath";
+
+  if ($type eq "LoadTCRunner") {
+    $testPath .= "/tc/profiles/load/$name";
+  } elsif ($type eq "RegressionRunner") {
+    $testPath .= "/tc/profiles/tc/$name";
+  } elsif ($type eq "RegressionLoadRunner") {
+    croak "RegressionLoadRunner tests are not supported!";
+  } elsif ($type eq "RegressionTSRunner") {
+    $testPath .= "/tc/profiles/ts/$name";
+  } # if
+
+  return 0 if !-f $testPath;
+
+  # Get test's name. Testname is stored in the profile file with a
+  # .script at the end. This later useful when trying to find the
+  # logfile as test name, not test filename, is used as part of the
+  # component of the path of where the logfile will be written.
+  my @lines = `strings $testPath | grep '\\.script'`;
+
+  if ($? == 0 && $lines[0] =~ /(\S+)\.script$/) {
+    $self->{testName} = $1;
+
+    # We're looking for the leaf name therefore strip off everything
+    # up to the last slash. For example, foo/bar/testname.scipt should
+    # result in "testname".
+    if ($self->{testName} =~ /.*\/(\S+)/) {
+      $self->{testName} = $1;
+    } # if
+  } # if
+
+  return 1;
+} # testExists
+
+############################################################################
+#
+# getLogFileContents:  Returns an array of the lines in the log file.
+#
+# Parms:               none
+#
+# Returns:             Array of lines from the "logical" logfile
+#
+############################################################################
+sub getLogFileContents ($) {
+  my ($self, $logFileName) = @_;
+
+  # Get timestamp: A porition of the path to the log file is actually
+  # a timestamp of the format MM.DD.YY_HH.MM.SS.MMM. It's difficult to
+  # tell what this timestamp will become so we use the following
+  # hueristic: We do an "ls -t $logFileName | head -1" on the remote
+  # system. This should give us the most recently modified
+  # file. Hopefully this will be the log file. However if multiple
+  # processes are writing in this directory then there is the
+  # possibility that our guess is wrong.
+  my @lines = `ls -t $logFileName 2> /dev/null`;
+
+  if ($? != 0) {
+    error "Unable to ls -t $logFileName";
+
+    LogDebug "BUG CATCHER: Here are the currently running java processes\n";
+    @lines = `ps -efww | grep java | grep -v \'grep java\'`;
+
+    LogDebug $_ foreach (@lines);
+
+    return undef;
+  } # if
+
+  chomp $lines[0];
+
+  # Get a list of logfiles
+  $logFileName .= "/" . $lines[0] . "/detailedlogs/*_logs_*";
+
+  @lines       = ();
+  my @logfiles = `ls $logFileName 2> /dev/null`;
+
+  chomp @logfiles;
+
+  foreach (@logfiles) {
+    # Logfiles still contain binary stuff so use strings(1)
+    my @logLines = `strings $_`;
+
+    chomp @logLines;
+
+    push @lines, @logLines;
+  } # foreach
+
+  return @lines;
+} # getLogFileContents
+
+############################################################################
+#
+# getLogFile:  Returns an array of the lines in the log file. Turns out
+#              that EAST creates a $self->{testName}_logs_1 file until
+#              it gets too large then creates a $self->{testName}_logs_2
+#              logfile and so on. So we want to present one logical file
+#              from n number of log files.
+#
+# Parms:       none
+#
+# Returns:     Array of lines from the "logical" logfile
+#
+############################################################################
+sub getLogFile () {
+  my ($self) = @_;
+
+  # Bail out if testName not set
+  return () if !$self->{testName};
+
+  # Start path
+  my $logFileName = "$ENV{MNT_DIR}/$ENV{EAST_REL}/DUT/$self->{unitType}$self->{unitNbr}/data/logs/";
+
+  # Add on path as per type of test
+  if ($self->{class} eq "load") {
+    $logFileName .= "load/testcase/$self->{testName}";
+  } elsif ($self->{class} eq "tc") {
+    $logFileName .= "regression/testcase/$self->{testName}";
+  } elsif ($self->{class} eq "ts") {
+    # Testsuites can have "parts"
+    $logFileName .= "regression/testsuite";
+
+    my @lines;
+    my @logfiles = `ls $logFileName 2> /dev/null`;
+
+    chomp @logfiles;
+
+    if (scalar @logfiles > 0) {
+      foreach (@logfiles) {
+       my @logLines = $self->getLogFileContents ("$logFileName/$_");
+
+       push @lines, @logLines;
+      } # foreach
+
+      return @lines;
+    } # if
+  } elsif ($self->{class} eq "pool") {
+    croak "Pool test type not implemented";
+  } else  {
+    croak "Invalid test case type $self->{class} found";
+  } # if
+
+  return $self->getLogFileContents ($logFileName);
+} # getLogFile
+
+############################################################################
+#
+# testResult:  Checks the test's logfile to determine the result
+#
+# Parms:
+#   name:      Name of test
+#
+# Returns:     A status - 0 if we are able to get the results, 1 if we
+#              can't - and a message of "Success", "Failure", "Incomplete"
+#              or an error message
+#
+############################################################################
+sub testResult ($) {
+  my ($self, $name) = @_;
+
+  my @lines = grep (/EXECUTION STATUS/, $self->getLogFile);
+
+  my $testResult = "Incomplete";
+
+  # Search for EXECUTION STATUS. Note there may be more than one
+  # EXECUTION STATUS in the array. If so return the last one.
+  if (scalar @lines > 0 && $lines[$#lines] =~ /EXECUTION STATUS :: (.*)/) {
+    $testResult = $1;
+    $testResult =~ s/\s+$//;
+  } # if
+
+  return (0, $testResult);
+} # testResult
+
+############################################################################
+#
+# shell:       Execute a shell script returning the results.
+#
+# Parms:
+#   script:    Script to run.
+#   opts:      Additional options passed to script
+#
+# Returns:      $status of shell exeuction and @lines of output
+#
+############################################################################
+sub shell ($;$@) {
+  my ($self, $script, @opts) = @_;
+
+  my ($status, @output) = Execute ($script . join " ", @opts);
+
+  $status >>= 8;
+
+  return ($status, @output);
+} # shell
+
+############################################################################
+#
+# stackOptions:        Stacks options into an array. This is mainly here to handle
+#              options that are quoted. Given a string of options like
+#              'foo -bar "quoted value"' a simple split /\s+/, $str would
+#              result in:
+#
+#              0 'foo'
+#              1 '-bar'
+#              2 '"quoted'
+#              3 'value"'
+#
+#              using this function we'll get:
+#
+#              0 'foo'
+#              1 '-bar'
+#              2 'quoted value'
+#
+#              instead.
+#
+# Parms:
+#   str                String of options to stack
+#
+# Returns:     Array of options stacked with quoted strings occupying a
+#              single slot in the array.
+#
+# Notes:       Doesn't balance quotes. Also, you can use () instead of ""
+#              (e.g. -if (condition is specified here)).
+#
+############################################################################
+sub stackOptions ($) {
+  my ($options) = @_;
+
+  my (@opts, $str);
+
+  my $hitString = 0;
+
+  foreach (split /\s+/, $options) {
+    if ($hitString) {
+      if (/(\S*)[\"|\'|\)]$/) {
+       $str .= $str ? " $1" : $1;
+       $hitString = 0;
+
+       push @opts, $str;
+
+       undef $str;
+      } else {
+       $str .= $str ? " $_" : $_;
+      } # if
+
+      next;
+    } else {
+      # Handle situation where you got only one "word"
+      if (/[\"|\'|\(](\S*)[\"\'\)]/) {
+       push @opts, $1;
+      } elsif (/[\"|\'|\(](\S*)/) {
+       $str .= $str ? " $1" : $1;
+       $hitString = 1;
+      } else {
+       push @opts, $_;
+      } # if
+    } # if
+  } # foreach
+
+  return @opts;
+} # stackOptions
+
+############################################################################
+#
+# rantvl:      Start rantvl
+#
+# Parms:
+#   cmd:       Rantvl command to execute
+#
+# Returns:      $pid of rantvl process and a message
+#
+############################################################################
+sub rantvl ($) {
+  my ($self, $cmd) = @_;
+
+  my $logged_in                = 0;
+  my $timedout         = 0;
+  my $logging_started  = 0;
+  my @lines;
+
+  # First establish an ssh session to RANHOST as RANUSER. Note we are
+  # assuming that pre-shared key ssh access has already been set up
+  # here.
+  $self->{rantvl} = new Expect ("ssh " . RANUSER . "\@" . RANHOST);
+
+  return (1, "Unable to connect to " . RANHOST . " as " . RANUSER)
+    unless $self->{rantvl};
+
+  $self->{rantvl}->log_user (get_debug);
+
+  $self->{rantvl}->expect (
+    $self->{timeout},
+
+    [ PROMPT,
+      sub {
+       $logged_in = 1;
+      }
+    ],
+
+    [ timeout =>
+      sub {
+       $timedout = 1;
+      }
+    ],
+  );
+
+  if ($timedout) {
+    return (1, "Timed out when connecting to " . RANHOST . " as " . RANUSER);
+  } elsif (!$logged_in) {
+    return (1, "Unable to connect to " . RANHOST . " as ". RANUSER);
+  } # if
+
+  # Get test options. It seems GetOptions doesn't support taking input
+  # from anything but @ARGV so we'll have to save a copy and restore
+  # it.  See eastUsage for more info.
+  my $rantvlTimeout    = $self->{timeout};
+  my @savedOptions     = @ARGV;
+  @ARGV                        = stackOptions $cmd;
+
+  # Don't complain about unknown options
+  Getopt::Long::Configure "pass_through";
+
+  # Only really care about timeout...
+  GetOptions (
+    "timeout=i", \$rantvlTimeout,
+  );
+
+  # Reassemble $cmd after GetOptions has processed it
+  $cmd = join " ", @ARGV;
+  @ARGV        = @savedOptions;
+
+  # Now start rantvl
+  $self->{rantvl}->send ("$cmd\n");
+
+  $self->{rantvl}->expect (
+    $rantvlTimeout,
+
+    [ qr"^Our pid is ",
+      sub {
+       my $pid = $_[0]->after;
+
+       if ($pid =~ /(\d+)/) {
+         $logging_started = $1;
+       } # if
+      }
+    ],
+
+    [ PROMPT,
+      sub {
+       my @output = split /\n/, $_[0]->before;
+
+       foreach (@output) {
+         chomp;
+         chop if /\r$/;
+         push @lines, $_;
+       } # foreach
+      }
+    ],
+
+    [ timeout =>
+      sub {
+       $timedout = 1;
+      }
+    ],
+  );
+
+  if ($logging_started) {
+    return ($logging_started, "Logging started");
+  } elsif ($timedout) {
+    return (0, "Timed out executing rantvl");
+  } else {
+    return (0, join "\n", @lines);
+  } #if
+} # rantvl
+
+############################################################################
+#
+# rendezvous:  Rendezvous with EAST process by searching the log file for
+#              a specific phrase. We will use $self->{timeout} to determine
+#              how long we are gonna wait for this phrase to appear. We
+#              will divide $self->{timeout} by 10, making 10 attempts. So
+#              with a default timeout of 180 seconds, we will try 10 times
+#              18 seconds apart, for the phrase to appear before timing
+#              out.
+#
+# Parms:
+#   phrase:    Phrase to search for
+#   timeout:   How long to time out waiting for the rendezvous
+#
+# Returns:     undef if rendezvous was successful - error message
+#              otherwise.
+#
+############################################################################
+sub rendezvous ($;$) {
+  my ($self, $phrase, $timeout) = @_;
+
+  my $status;
+
+  my $attempts = 0;
+
+  $timeout = $timeout ? $timeout : $self->{timeout};
+
+  while (!$status && $attempts++ < 10) {
+    display_nolf "Attempt #$attempts" if get_debug;
+
+    my @lines = grep (/$phrase/, $self->getLogFile);
+
+    last if scalar @lines > 0;
+
+    display " sleeping " . $timeout / 10 . " seconds" if get_debug;
+    sleep $timeout / 10;
+  } # while
+
+  if ($attempts > 10) {
+    return "Timed out";
+  } else {
+    return undef;
+  } # if
+} # rendezvous
+
+############################################################################
+#
+# connected:   Checks to see if you're connected to EAST
+#
+# Parms:
+#   none
+#
+# Returns:     undef if connected - error message otherwise
+#
+############################################################################
+sub connected () {
+  my ($self) = @_;
+
+  my $serverLogPath    = "$ENV{MNT_DIR}/$ENV{EAST_REL}/DUT/$self->{unitType}$self->{unitNbr}/data/logs/Server_Logs";
+  my $serverLog                = $self->{unitType} eq "rbs"
+                       ? "$serverLogPath/rnc_aal2.log"
+                       : "$serverLogPath/nodeb_aal2_utran.log";
+  my $searchStr                = "Successfully connected to EventServer";
+  my $cmd              = "grep -q \"$searchStr\" $serverLog > /dev/null 2>&1";
+  my @lines;
+
+  # We'll try up to 2 minutes, every 5 seconds...
+  my $timedout = 0;
+
+  while ($timedout < (60 * 2)) {
+    @lines = `$cmd`;
+
+    last if $? == 0;
+
+    sleep 5;
+
+    $timedout += 5;
+  } # while
+
+  return "Timed out while attempting to rendezvous with server"
+    if $timedout >= (60 * 2);
+
+  # Get RBS/RNC version string Must translate unitType and unitNbr
+  # into a machine name of the form "ran{type}{nbr}" but we refer to
+  # to things as 1-7 and they want things like 01-07. So we do
+  # "ran{type}0{nbr}" give us things like ranrbs01 or ranrnc03.
+
+  # Here's another instance where using DNS aliases are messing us up.
+  # Pat Phelps was testing on -unit 3m2. But that would have made
+  # $machine = ranrnc03m2 and the "grep ^$machine below would fail. So
+  # for a kludge we simply substr the first character of
+  # $self->{unitNbr}.
+  my $machine = "ran$self->{unitType}0" . substr $self->{unitNbr}, 0, 1;
+
+  $cmd  = "/prj/muosran/SWIT/moshell/swstat ";
+  $cmd .= "/prj/muosran/SWIT/moshell/sitefiles/$machine ";
+
+  # Here we are grepping for lines begining with ^$machine, however
+  # there are more than one, hence the tail -1.
+  $cmd .= "| grep ^$machine | tail -1";
+
+  @lines = $self->{msh}->exec ($cmd);
+
+  # For some reason we are sometimes getting junk in $lines [0] so
+  # filter out lines that don't have ^$machine in it.
+  @lines = grep (/^$machine/, @lines);
+
+  if ($lines[0] && $lines[0] =~ /\w+\s+(\w+)/) {
+    my $rstate = $1;
+
+    my $build_no = Utils->getLoadFromRState ($rstate);
+
+    $self->{ran_version} = uc ($self->{unitType}) . ":$rstate-$build_no";
+  } # if
+
+  return undef;
+} # connected
+
+############################################################################
+#
+# connect:     Connects to the remote East machine
+#
+# Parms:
+#   view:      View name to set to to run the the test
+#   unitType:  Type of unit (rbs, rnc or east)
+#   unitNbr:   Number of the unit
+#   tm500:     Name of tm500 view (if any)
+#   nms:       Name of nms view (if any)
+#
+# Returns:     Undefined if connection was successful or error message if
+#              not
+#
+############################################################################
+sub connect ($$$;$$$$) {
+  my ($self, $view, $unitType, $unitNbr, $tm500, $nms, $feature, $secure) = @_;
+
+  $self->{unitType} = lc $unitType;
+
+  croak "ERROR: Type must be rbs, rnc or east"
+    unless $self->{unitType} eq "rbs" or
+          $self->{unitType} eq "rnc" or
+           $self->{unitType} eq "east";
+
+  $self->{unitNbr} = $unitNbr;
+
+  # Check if unit is in use
+  my $msg = $self->inUse;
+
+  return $msg if $msg;
+
+  # Check that view exists
+  my ($status, @lines) = $self->viewExists ($view);
+
+  return "View $view does not exist" if $status;
+
+  # Save $view - we'll need it later...
+  $self->{view} = $view;
+
+  if ($self->{view} =~ /(\S+)_SIM/) {
+    $self->{userdir} = $1;
+  } else {
+    croak "ERROR: Unable to find userdir";
+  } # if
+
+  # Connect as RANUSER@RANHOST and store the connection. We'll need
+  # this to secure the node and we'll need this later on too.
+  debug "Connecting to ". RANHOST . " as " . RANUSER;
+
+  $self->{msh} = new Rexec (
+    host       => RANHOST,
+    username   => RANUSER,
+    prompt     => PROMPT,
+  );
+
+  error "Unable to connect to " . RANHOST . " as " . RANUSER, 1
+    unless $self->{msh};
+
+  # Secure node
+  if ($secure) {
+    my $node   = "$self->{unitType}$self->{unitNbr}";
+
+    # We need to wait for a while since this securenode command takes
+    # a while. Looking briefly, securenode took 4'51" to run. So we'll
+    # wait up to... 10 minutes...
+    my $secureNodeTimeoutMinutes = 10;
+    my $secureNodeTimeoutSeconds = $secureNodeTimeoutMinutes * 60;
+
+    verbose "Attempting to secure node $node - This make take a while...\n"
+          . "(Will timeout in $secureNodeTimeoutMinutes minutes)";
+
+    my @lines  = $self->{msh}->exec ("/prj/muosran/SWIT/tools/bin/securenode $node", $secureNodeTimeoutSeconds);
+    my $status = $self->{msh}->status;
+
+    if ($status != 0) {
+      if ($status == 1) {
+       error "The node $node is not known", $status;
+      } elsif ($status == 2) {
+       error "The node $node is not responding", $status;
+      } elsif ($status == 3) {
+       error "Unable to secure $node", $status;
+      } elsif ($status == -1) {
+       error "Timed out attempting to secure node $node", $status;
+      } else {
+       error "Unknown error has occurred", $status;
+      } # if
+    } else {
+      verbose "Node $node secured";
+    } # if
+  } # if
+
+  debug "Starting $unitType on unit $unitNbr";
+
+  my $cmd = "$self->{unitType} $self->{unitNbr}";
+
+  my $start_str                = "StaRT";
+  my $errno_str                = "ReXeCerRoNO=\$?";
+  my $compound_cmd     = "echo $start_str; $cmd; echo $errno_str";
+
+  $self->{remote} = new Expect ($compound_cmd);
+
+  $self->{remote}->log_user (get_debug);
+
+  my $result;
+
+  @lines = ();
+
+  $self->{remote}->expect (
+    $self->{timeout},
+
+    [ timeout =>
+      sub {
+       my $exp         = shift;
+       my $before      = $exp->before;
+       my $after       = $exp->after;
+       push @lines, "$cmd timed out";
+       $result = -1;
+      }
+    ],
+
+    [ qr "$start_str",
+      sub {
+       exp_continue;
+      }
+    ],
+
+    [ qr "$errno_str",
+      sub {
+       my $exp         = shift;
+       my $before      = $exp->before;
+       my $after       = $exp->after;
+       
+       if ($after =~ /(\d+)/) {
+         $status = $1;
+       } # if
+
+       my @output = split /(\n\r)/, $before;
+
+       foreach (@output) {
+         chomp;
+         chop if /\r$/;
+         last if /$errno_str=/;
+         next if /^$/;
+         push @lines, $_;
+       } # foreach
+
+       exp_continue;
+      }
+    ],
+
+    [ $self->{prompt},
+      sub {
+        debug "Hit prompt";
+      }
+    ],
+  );
+
+  return join "\n", @lines if $status != 0;
+
+  # Set prompt to something distinctive
+  $self->{prompt}      = "\@\@\@";
+  $cmd                 = "export PS1=$self->{prompt}\n";
+
+  $self->{remote}->send ($cmd);
+
+  $self->{remote}->expect (
+    $self->{timeout},
+
+    [ timeout =>
+      sub {
+       $result = "$cmd timed out";
+      }
+    ],
+
+    [ "^$self->{prompt}",
+      sub {
+        debug "Hit prompt";
+      }
+    ],
+  );
+
+  return $result if $result;
+
+  # Set TM500_VIEW if passed in
+  if ($tm500) {
+    $cmd = "export TM500_VIEW=$tm500\n";
+
+    $self->{remote}->send ($cmd);
+
+    $self->{remote}->expect (
+      $self->{timeout},
+
+      [ timeout =>
+        sub {
+         $result = "$cmd timed out";
+        }
+      ],
+
+      [ "^$self->{prompt}",
+        sub {
+          debug "Hit prompt";
+        }
+      ],
+    );
+
+    return $result if $result;
+  } # if
+
+  # Set NMS_VIEW if passed in
+  if ($nms) {
+    $cmd = "export NMS_VIEW=$nms\n";
+
+    $self->{remote}->send ($cmd);
+
+    $self->{remote}->expect (
+      $self->{timeout},
+
+      [ timeout =>
+        sub {
+         $result = "$cmd timed out";
+        }
+      ],
+
+      [ "^$self->{prompt}",
+        sub {
+          debug "Hit prompt";
+        }
+      ],
+    );
+
+    return $result if $result;
+  } # if
+
+  # Set FEATURE if passed in
+  if ($feature) {
+    $cmd = "export FEATURE=$feature\n";
+
+    $self->{remote}->send ($cmd);
+
+    $self->{remote}->expect (
+      $self->{timeout},
+
+      [ timeout =>
+        sub {
+         $result = "$cmd timed out";
+        }
+      ],
+
+      [ "^$self->{prompt}",
+        sub {
+          debug "Hit prompt";
+        }
+      ],
+    );
+
+    return $result if $result;
+  } # if
+
+  debug "Starting EAST CLI in view $self->{view} on $self->{unitType}$self->{unitNbr}";
+
+  $cmd         = "start_east_auto $self->{view} $self->{unitType}$self->{unitNbr}";
+  $compound_cmd        = "echo $start_str; $cmd; echo $errno_str";
+
+  my $attempts = 0;
+
+  $self->{remote}->send ("$compound_cmd\n");
+
+  $self->{remote}->expect (
+    $self->{timeout},
+
+    [ timeout =>
+      sub {
+       push @lines, "$cmd timed out";
+       $status = -1;
+      }
+    ],
+
+    [ qr "$start_str",
+      sub {
+        exp_continue;
+      }
+    ],
+
+    [ qr "$errno_str",
+      sub {
+       my $exp         = shift;
+       my $before      = $exp->before;
+       my $after       = $exp->after;
+       
+       if ($after =~ /(\d+)/) {
+         $status = $1;
+       } # if
+
+       my @output = split /(\n\r)/, $before;
+
+       foreach (@output) {
+         chomp;
+         chop if /\r$/;
+         last if /$errno_str=/;
+         next if /^$/;
+         push @lines, $_;
+       } # foreach
+
+       exp_continue;
+      }
+    ],
+
+    [ $self->{prompt},
+      sub {
+        debug "Hit prompt";
+      }
+    ],
+  );
+
+  unless ($status == 0) {
+    return "Unable to execute $cmd" . join "\n", @lines;
+  } else {
+    return $self->connected;
+  } # if
+} # connect
+
+############################################################################
+#
+# eastUsage:   Displays East command options
+#
+# Parms:
+#   msg:       Usage message
+#
+# Returns:     1 for failure
+#
+############################################################################
+sub eastUsage (;$) {
+  my ($msg) = @_;
+
+  my $usage = "ERROR: $msg\n\n" if $msg;
+
+  $usage .= <<END;
+Usage: East::exec (<test class> <testname> <opts>)
+
+Where <opts>:
+
+\t[-activecalls <n>]
+\t[-displaylevel <n>]
+\t[-executionlevel <n>]
+\t[-loglevel <n>]
+\t[-mode <admin|local>]
+\t[-p <property=value>]
+\t[-runnerid <id>]
+\t[-testbed <name>]
+\t[-testenvironment <testenvironmentname>]
+\t[-timeout <n>]
+\t[-pause <n>]
+
+  -timeout <n>         Specifies the timeout for this test's execution.
+                       If negative the test will be placed in the
+                       background. No result is recovered from
+                       background tests nor are any logfiles analysed
+                       or stored. If positive then this sets the
+                       timeout period for this test in seconds.
+
+  -pause <n>           Used in conjunction with -timeout. If test is
+                       backgrounded then $FindBin::Script will wait
+                       pause seconds before returning control from
+                       this test. This allows the backgrounded test
+                       time to start.
+
+  -name <name>         Names a test. Used in conditional execution.
+
+  -if (<name> <status>)        Run this test if the named test returned <status>
+                       where <status> is one of
+
+                         . Success
+                         . Failure
+                         . In Progress
+                         . Timed out
+                         . Failed to execute
+                         . Rendezvous
+                         . Failed to rendezvous
+
+Note: -flag is supported by setting the -timeout appropriately. Setting
+timeout <= 0 will result in -flag NOT being specified. Setting timeout
+> 0 will result in -flag being specified.
+
+Also -run is always set. After all, we're automation here! :-)
+
+For other options see "Command Line in EAST" for more info.
+END
+
+  display $usage;
+
+  return 1 if $msg;
+} # easeUsage
+
+############################################################################
+#
+# exec:                Executes a test remotely on East.
+#
+# Parms:
+#   opts       A reference to a hash of options
+#   results    A reference to a hash of execution results
+#
+# Note: $opts{timeout} can be set to the nNumber of seconds to wait
+# for test to finish. Default: DEFAULT_TIMEOUT seconds. Set to 0 to
+# indicate to wait forever. Note that timeout can be set per
+# individual exec of a test case or set view setTimeout for all future
+# test exec's or obtained via getTimeout.
+#
+# Returns:     0 for success, otherwise failure
+#
+############################################################################
+sub exec ($$) {
+  my ($self, $opts, $results) = @_;
+
+  my $testResult;
+
+  $self->{class} = lc $$opts{class};
+
+  # The log class is special - It means run rantvl - so we handled it
+  # differently here and then return quickly.
+  if ($self->{class} eq "log") {
+    # You'd think that /prj/muosran/SWIT/tools/bin would be in pswit's
+    # path...
+    my $cmd = "/prj/muosran/SWIT/tools/bin/$$opts{test}";
+
+    # Add unit and number
+    $cmd .= " -$self->{unitType} $self->{unitNbr}";
+
+    # Add flag to get pid
+    $cmd .= " -pid";
+
+    # Compose -logpath
+    $cmd .= " -logpath $self->{saveTo}";
+
+    # Now start up rantvl
+    my ($status, $msg) = $self->rantvl ($cmd);
+
+    # Status is reversed here. The rantvl subroutine returns the pid
+    # of the rantvl process for success - 0 for failure. So we flip
+    # the boolean here.
+    return !$status, $msg;
+  } elsif ($self->{class} eq "shell") {
+    # The shell class is also special. Here we execute any arbitrary
+    # shell command. Initially this has been implemented simply
+    # because of a request to be able to pause between test steps
+    # (e.g. sleep 10) but it was decided to make this pretty general
+    # so any shell command is acceptable. Note we do not evaluate the
+    # result of the execution or at least it does not influence the
+    # status of the test at this time.
+    my ($status, @lines) = $self->shell ($$opts{test});
+
+    if ($status == 0) {
+      return $status, "Success";
+    } else {
+      if (scalar @lines == 0) {
+       return $status, "Unknown error occurred while executing $$opts{test}";
+      } else {
+       return $status, join "\n", @lines;
+      } # if
+    } # if
+  } elsif ($self->{class} eq "manual") {
+    # The manual class will be similiar to the shell class except
+    # that its intent is to provide an environment for the user
+    # to run any number of manual tests and then return to rantest
+
+    # For the user's convenience - put $logpath into the environment
+    $ENV{LOGPATH} = LOGBASE . "/$self->{saveTo}";
+
+    display "Perform your manual tests - type exit when finished";
+
+    # Now run the user's shell
+    system ($ENV{SHELL});
+
+    print "Did your tests complete successfully? (y/N) ";
+
+    my $response = <STDIN>;
+
+    if ($response =~ /y/i) {
+      return 0, "Success";
+    } else {
+      return 1, "Manual test(s) failed";
+    } # if
+  } # if
+
+  my ($status, $msg) = validTestType ($self->{class});
+
+  return ($status, $msg) if $status;
+
+  # Convert short type names -> a valid test class
+  my $testClass = $_validTestTypes{$self->{class}};
+
+  my $runopts = "-log -run";
+
+  # Get test options. It seems GetOptions doesn't support taking input
+  # from anything but @ARGV so we'll have to save a copy and restore
+  # it.  See eastUsage for more info.
+  my @savedOptions = @ARGV;
+
+  @ARGV = stackOptions $$opts{test};
+
+  # These options should be reset and not linger from one test to the
+  # next.
+  undef $$opts{if};
+  undef $$opts{name};
+  undef $$opts{rendezvous};
+  undef $$opts{timeout};
+
+  # Default testbed to type & unit #
+  $$opts{testbed} = "$self->{unitType}$self->{unitNbr}";
+
+  $status = GetOptions (
+    $opts,
+    "activecalls=i",
+    "displaylevel=i",
+    "executionlevel=i",
+    "loglevel=i",
+    "mode=s",
+    "p=s",
+    "pause=i",
+    "runnerid=s",
+    "testbed=s",
+    "testenvironment=s",
+    "timeout=i",
+    "name=s",
+    "if=s",
+    "rendezvous=s",
+  );
+
+  if (!$status) {
+    $msg = "Unknown option";
+
+    eastUsage $msg;
+
+    return (1, $msg);
+  } # if
+
+  # Reassemble $$opts{test} after GetOptions has processed it
+  $$opts{test} = join " ", @ARGV;
+  @ARGV                = @savedOptions;
+
+  # Check other options:
+  if (defined $$opts{displaylevel} and
+      ($$opts{displaylevel} < 0 or
+       $$opts{displaylevel} > 6)) {
+    $msg = "displaylevel must be between 0-6";
+
+    eastUsage $msg;
+
+    return (1, $msg);
+  } # if
+
+  if (defined $$opts{executionlevel} and
+      ($$opts{executionlevel} < 0 or
+       $$opts{executionlevel} > 6)) {
+    $msg = "executionlevel must be between 0-6";
+
+    eastUsage $msg;
+
+    return (1, $msg);
+  } # if
+
+  return (1, "ERROR: Test $$opts{test} does not exist")
+    unless $self->testExists ($testClass, $$opts{test});
+
+  # If run sequentially then we add the -flag that says run the test
+  # then close the window - Odd I know... Otherwise we omit the -flag
+  # which will cause the test to run and the window to remain up.
+  $runopts .= " -flag" if !$$opts{timeout} || $$opts{timeout} > 0;
+
+  # Options that must appear in the front
+  my $frontopts = "-name $$opts{test}";
+  $frontopts   .= " -testbed $$opts{testbed}"                  if $$opts{testbed};
+  $frontopts   .= " -testenvironment $$opts{testenvironment}"  if $$opts{testenvironment};
+
+  # Process other options
+  $runopts .= " -activecalls $$opts{activecalls}"              if $$opts{activecalls};
+  $runopts .= " -displaylevel $$opts{displaylevel}"            if $$opts{displaylevel};
+  $runopts .= " -executionlevel $$opts{executionlevel}"                if $$opts{executionlevel};
+  $runopts .= " -mode $$opts{mode}"                            if $$opts{mode};
+  $runopts .= " -p $$opts{p}"                                  if $$opts{p};
+  $runopts .= " -runnerid $$opts{runnerid}"                    if $$opts{runnerid};
+
+  my $cmd = "java $testClass $frontopts $runopts";
+
+  $cmd .= "&" if $$opts{timeout} && $$opts{timeout} < 0 ||
+                 $$opts{rendezvous};
+
+  my $timeout = $$opts{timeout} && $$opts{timeout} > 0 ? $$opts{timeout} : $self->{timeout};
+
+  if ($$opts{if}) {
+    my @components     = split " ", $$opts{if};
+    my $testName       = shift @components;
+    my $result         = lc (join " ", @components);
+
+    if ($$results{$testName} && $$results{$testName} ne $result) {
+      $testResult = "Skipped";
+
+      $$results{$$opts{name}} = lc $testResult if $$opts{name};
+
+      return (1, $testResult);
+    } # if
+  } # if
+
+  debug "\nRunning $cmd";
+
+  my ($startTime, $attempts, $duration);
+
+  my $result = 0;
+
+  use constant MAX_ATTEMPTS => 4;
+
+  $attempts    = 0;
+  $duration    = 60;
+
+  my $expectBuffer;
+
+  do {
+    $startTime = time;
+    $attempts++;
+
+    $self->{remote}->send ("$cmd\n");
+
+    $self->{remote}->expect (
+      $timeout,
+
+      [ timeout =>
+        sub {
+         $result = -1;
+        }
+      ],
+
+      [ $self->{prompt},
+        sub {
+         my $exp       = shift;
+         my $before    = $exp->before;
+         my $after     = $exp->after;
+
+         $expectBuffer = "->$before<->$after<-";
+          debug "Hit prompt";
+        }
+      ],
+    );
+
+    $duration = time - $startTime;
+
+    if ($duration < 2 and $attempts > 0) {
+      if ($cmd !~ /&$/) {
+       if ($$opts{file}) {
+         LogDebug "File: $$opts{file}";
+       } else {
+         LogDebug "File: Not set";
+       } # if
+       LogDebug "That happened too quickly! Attempt #$attempts of " . MAX_ATTEMPTS . " to restart cmd (Duration: $duration)\n$cmd\n";
+       LogDebug "Contents of expect buffer:\n$expectBuffer";
+       warning "That happened too quickly! Attempt #$attempts of " . MAX_ATTEMPTS . " to restart cmd\n$cmd\n";
+       display "The following is debug output:";
+       display "-" x 80;
+       display "Contents of expect buffer:\n$expectBuffer";
+       display "-" x 80;
+       display "End of debug output";
+      } # if
+    } # if
+
+    unless ($duration > 2 or $attempts >= MAX_ATTEMPTS or $cmd =~ /&$/) {
+      LogDebug "Looping around for another try\n";
+    } # unless
+  } until ($duration > 2 or $attempts >= MAX_ATTEMPTS or $cmd =~ /&$/);
+
+  if ($result == -1) {
+    # Timed out. Kill stuck process
+    $self->{remote}->send ("\cC");
+
+    $self->{remote}->expect (
+      $timeout,
+
+      [ $self->{prompt},
+        sub {
+         debug "Hit prompt";
+       }
+      ],
+    );
+
+    return (-1, "Timed out");
+  } # if
+
+  # If we backgrounded ourselves then there's no real status to
+  # retrieve - we must just hope for the best.
+  if ($cmd =~ /&$/) {
+    # Pause to allow test to start up.
+    my $pause = $$opts{pause} ? $$opts{pause} : 0;
+
+    debug "Sleeping $pause seconds";
+    sleep $pause;
+    debug " Gee that was refressing!";
+
+    if ($$opts{rendezvous}) {
+      if ($self->rendezvous ($$opts{rendezvous}, $$opts{timeout})) {
+       $testResult = "Unable to rendezvous";
+
+       $$results{$$opts{name}} = lc $testResult if $$opts{name};
+
+       return (1, $testResult);
+      } else {
+       $testResult = "Rendezvous";
+
+       $$results{$$opts{name}} = lc $testResult if $$opts{name};
+
+       return (0, $testResult);
+      } # if
+    } else {
+      $testResult = "In progress";
+
+      $$results{$$opts{name}} = lc $testResult if $$opts{name};
+
+      return (0, $testResult);
+    } # if
+  } # if
+
+  ($status, $testResult) = $self->testResult ($$opts{test});
+
+  $$results{$$opts{name}} = lc $testResult if $$opts{name};
+
+  # Get TM500 version used (if any)
+  delete $self->{tm500_version};
+
+  my @logLines = $self->getLogFile;
+  my @lines    = grep (/^Command:.*version/, @logLines);
+
+  if ($lines[0] && $lines[0] =~ /\-\-version\s+(.+)/) {
+    $self->{tm500_version} = $1;
+  } # if
+
+  @lines = grep (/^Simulator version is/, @logLines);
+
+  if ($lines[0] && $lines[0] =~ /Simulator version is\s+(.+)\./) {
+    $self->{nms_version} = $1;
+  } # if
+
+  return ($status, $testResult);
+} # exec
+
+############################################################################
+#
+# disconnect:  Disconnects from East simulator
+#
+# Parms:       none
+#
+# Returns:     nothing
+#
+############################################################################
+sub disconnect {
+  my ($self) = @_;
+
+  if ($self->{rantvl}) {
+    # Send Control-C to terminate any processes running
+    $self->{rantvl}->send ("\cC");
+
+    # Try to exit remote command
+    $self->{rantvl}->send ("exit\n");
+
+    # Try a hard close
+    $self->{rantvl}->hard_close;
+
+    # Let's remember that we were connected so we know in
+    # collectLogFiles that we need to collect the rantvl log files.
+    $self->{collectRantvl} = 1;
+
+    # Call destructor on Expect process
+    undef $self->{rantvl};
+  } # if
+
+  if ($self->{remote}) {
+    # Send Control-C to terminate any processes running
+    $self->{remote}->send ("\cC");
+
+    # Try to exit remote command
+    $self->{remote}->send ("exit\n");
+
+    # Try a hard close
+    $self->{remote}->hard_close;
+
+    # Call destructor on Expect process
+    undef $self->{remote};
+  } # if
+} # disconnect
+
+############################################################################
+#
+# getCollectLogFiles:  Gets CollectLogFiles
+#
+# Parms:               None
+#
+# Returns:             collectLogFiles setting
+#
+############################################################################
+sub getCollectLogFiles () {
+  my ($self) = @_;
+
+  return $self->{collectLogFiles};
+} # getCollectLogFiles
+
+############################################################################
+#
+# setCollectLogFiles:  Sets CollectLogFiles to notate that we need to
+#                      collect log files
+#
+# Parms:               
+#   collectLogFiles:   Boolean indictating where or not to collect log 
+#                      files
+#
+# Returns:             
+#   Old collectLogFiles setting
+#
+############################################################################
+sub setCollectLogFiles ($) {
+  my ($self, $collectLogFiles) = @_;
+
+  my $old = $self->{collectLogFiles};
+
+  $self->{collectLogFiles} = $collectLogFiles;
+
+  return $old;
+} # setCollectLogFiles
+
+############################################################################
+#
+# setRantvlStartTime:  Sets rantvlStartTime to notate that we need to
+#                      collect alarms
+#
+# Parms:               
+#   startTime:         Start time (from time())
+#
+# Returns:             
+#   Nothing
+#
+############################################################################
+sub setRantvlStartTime ($) {
+  my ($self, $startTime) = @_;
+
+  $self->{rantvlStartTime} = $startTime;
+} # setRantvlStartTime
+
+############################################################################
+#
+# setTestCaseID:       Sets TestCaseID for later use by collectLogFiles
+#
+# Parms:               TestCaseID
+#
+# Returns:             Nothing
+#
+############################################################################
+sub setTestCaseID ($) {
+  my ($self, $testCaseID) = @_;
+
+  $self->{testCaseID} = $testCaseID;
+} # setTestCaseID
+
+############################################################################
+#
+# setSaveTo:   Sets saveTo for later use by collectLogFiles
+#
+# Parms:       
+#   path:      Path to save things to
+#
+# Returns:     Nothing
+#
+############################################################################
+sub setSaveTo ($) {
+  my ($self, $saveTo) = @_;
+
+  $self->{saveTo} = $saveTo;
+} # setSaveTo
+
+############################################################################
+#
+# getSaveTo:   Gets saveTo
+#
+# Parms:       None
+#
+# Returns:     saveTo path
+#
+############################################################################
+sub getSaveTo ($) {
+  my ($self) = @_;
+
+  return $self->{saveTo};
+} # getSaveTo
+
+############################################################################
+#
+# getTimeout:  Returns the timeout value for the remote execution object
+#              (if connected)
+#
+# Parms:       none
+#
+# Returns:     Timeout value for remote execution object, if connected, or
+#              undefined.
+#
+############################################################################
+sub getTimeout () {
+  my ($self) = @_;
+
+  return $self->{remote}->getTimeout if $self->{remote}
+} # getTimeout
+
+############################################################################
+#
+# setTimeout:  Sets timeout value for remote execution object for all
+#              subsequent exec's.
+#
+# Parms:
+#   timeout:   new timeout value
+#
+# Returns:     Old timeout value (if previously connected)
+#
+############################################################################
+sub setTimeout ($) {
+  my ($self, $timeout) = @_;
+
+  return $self->{remote}->setTimeout ($timeout) if $self->{remote};
+} # setTimeout
+
+############################################################################
+#
+# _checkOutElement:    Checks out, or creates initial version of the passed
+#                      in file into Clearcase
+#
+# Parms:
+#   file:              Name of file to checkout (mkelem)
+#
+# Returns:             0 if successful - non-zero if unsuccessful
+#
+############################################################################
+sub _checkOutElement ($;$) {
+  my ($file, $eltype) = @_;
+
+  my $parentDir = dirname $file;
+
+  my ($status, @lines);
+
+  # If the file already exists attempt to check it out
+  if (-f $file) {
+    # Assuming a snapshot view so run update
+    ($status, @lines) = Execute CLEARTOOL . " update -log /dev/null $file 2>&1";
+
+    $status >>= 8;
+
+    error ("Unable to update view (Errno: $status)\n" . join ("\n", @lines), 1)
+      unless $status == 0;
+
+    $status >>= 8;
+
+    ($status, @lines) = Execute CLEARTOOL . " checkout -nc $file 2>&1";
+
+    $status >>= 8;
+
+    error ("Unable to checkout $file (Errno: $status)\n" . join ("\n", @lines), 1)
+      unless $status == 0;
+  } else {
+    ($status, @lines) = Execute CLEARTOOL . " checkout -nc $parentDir 2>&1";
+
+    $status >>= 8;
+
+    error ("Unable to checkout parent directory $parentDir (Errno: $status)\n" . join ("\n", @lines), 1)
+      unless $status == 0;
+
+    # set eltype if passed
+    my $eltypeParm = $eltype ? "-eltype $eltype" : "";
+    
+    # create the new element
+    ($status, @lines) = Execute CLEARTOOL . " mkelem $eltypeParm -nc $file 2>&1";
+
+    $status >>= 8;
+
+    error ("Unable to mkelem $file (Errno: $status)\n" . join ("\n", @lines), 1)
+      unless $status == 0;
+
+    # Check in parent directory so we don't have to worry about it later
+    ($status, @lines) = Execute CLEARTOOL . " checkin -nc $parentDir 2>&1";
+
+    $status >>= 8;
+
+    error ("Unable to checkin parent directory $parentDir (Errno: $status)\n" . join ("\n", @lines), 1)
+      unless $status == 0;
+  } # if
+
+  return $status;
+} # _checkOutElement
+
+############################################################################
+#
+# _checkInElement:     Checks in the passed in file into Clearcase
+#
+# Parms:
+#   file:              Name of file to checkin
+#
+# Returns:             0 if successful - 1 if unsuccessful
+#
+############################################################################
+sub _checkInElement ($) {
+  my ($element) = @_;
+
+  my ($status, @lines) = Execute CLEARTOOL . " checkin -nc $element 2>&1";
+
+  $status >>= 8;
+
+  error ("Unable to checkin $element (Errno: $status)\n" . join ("\n", @lines), 1)
+    unless $status == 0;
+} # _checkInElement
+
+############################################################################
+#
+# _mkDirElement:       Creates a directory element in Clearcase
+#
+# Parms:
+#   dir:               Name of the directory to mkelem
+#
+# Returns:             0 if successful - 1 if unsuccessful
+#
+############################################################################
+sub _mkDirElement ($) {
+  my ($dir) = @_;
+
+  return 0 if -d $dir;
+
+  my $parentDir = dirname $dir;
+
+  my ($status, @lines) = Execute CLEARTOOL . " checkout -nc $parentDir 2>&1";
+
+  $status >>= 8;
+
+  error ("Unable to checkout parent directory $parentDir (Errno: $status)\n" . join ("\n", @lines), 1)
+    unless $status == 0;
+
+  eval { mkpath $dir };
+
+  error "Unable to mkpath $dir\n$@", 1 if $@;
+
+  ($status, @lines) = Execute CLEARTOOL . " mkelem -nc -nco $dir 2>&1";
+
+  $status >>= 8;
+
+  error ("Unable to mkdir $dir (Errno: $status)\n" . join ("\n", @lines), 1)
+    unless $status == 0;
+
+  return _checkInElement $parentDir;
+} # _mkDirElement
+
+############################################################################
+#
+# _makeTar:    Creates a tarfile
+#
+# Parms:
+#   file:      Name of tarfile to create
+#   path:      Path to use in the tarfile
+#   files:     Files to tar up
+#
+# Returns:     0 if successful - 1 if unsuccessful
+#
+############################################################################
+sub _makeTar ($;$$) {
+  my ($file, $path, $files) = @_;
+
+  $path = "." unless $path;
+
+  eval { mkpath $path };
+
+  error "Unable to mkpath $path\n$@", 1 if $@;
+
+  my ($status, @lines) = Execute "tar -czf $file -C $path $files";
+
+  $status >>= 8;
+
+  error ("Unable to create tarfile $file (Errno: $status)\n" . join ("\n", @lines), 1)
+    unless $status == 0
+} # _makeTar
+
+############################################################################
+#
+# makeBaselinesReadme  Creates a baselines.readme file
+#
+# Parms:
+#   file:              Name of file to create
+#
+# Returns:             0 if successful - 1 if unsuccessful
+#
+############################################################################
+sub makeBaselinesReadme ($) {
+  my ($self, $file) = @_;
+
+  open FILE, ">$file"
+    or error "Unable to open $file - $!", return 1;
+
+  my ($status, @lines) = Execute CLEARTOOL . " lsstream -fmt \"\%[found_bls]p\" -view $self->{view}";
+
+  $status >>= 8;
+
+  error ("Unable to get baselines (Errno: $status)\n" . join ("\n", @lines), 1)
+    unless $status == 0;
+
+  print FILE "$_\n" foreach (split (" ", $lines[0]));
+
+  close FILE;
+
+  return 0;
+} # makeBaselinesReadme
+
+############################################################################
+#
+# fixUpLogs:   Fix up RNC log files (hotfix)
+#
+# Parms:       none
+#
+# Returns:     0 if successful - 1 if unsuccessful
+#
+############################################################################
+sub fixUpLogs () {
+  my ($self) = @_;
+
+  my ($status, @lines);
+
+  # Copy over the necessary log files
+  my $file     = $self->{unitType} eq "rbs"
+               ? "rnc_aal5.log"
+               : "nodeb_aal5_utran.log";
+  my $from     = LOGBASE . "/$self->{saveTo}/EASTLogs/Server_Logs/$file";
+  my $to       = "/tmp/$file.$$";
+  my $eastfile = $to;
+
+  unless (-f $from) {
+    error "Unable to find $file file";
+    return 1;
+  } # unless
+
+  my $cmd = "scp -q $from " . RANUSER . "\@" . RANHOST . ":$to";
+
+  ($status, @lines) = Execute $cmd;
+
+  $status >>= 8;
+
+  if ($status != 0) {
+    error ("Unable to execute command: $cmd\n" . join ("\n", @lines));
+    return $status;
+  } # if
+
+  my $rnclog = "RNCLog.txt";
+
+  $file = $self->{unitType} eq "rbs"
+       ? "RBSLog.txt"
+       : "RNCLog.txt";
+  $from = LOGBASE . "/$self->{saveTo}/Rantvl/$file";
+  $to  = "/tmp/$file.$$";
+
+  my $logfile = $to;
+
+  unless (-f $from) {
+    error "Unable to find $file file";
+    return 1;
+  } # unless
+
+  $cmd = "scp -q $from " . RANUSER . "\@" . RANHOST . ":$to";
+
+  ($status, @lines) = Execute $cmd;
+
+  $status >>= 8;
+
+  if ($status != 0) {
+    error ("Unable to execute command: $cmd\n" . join ("\n", @lines));
+    return $status;
+  } # if
+
+  $status = rename $from, "$from.orig";
+
+  unless ($status) {
+    error "Unable to rename $from -> $from.orig";
+    return 1;
+  } # unless
+
+  (my $buildNbr) = $self->{ran_version} =~ /.*-(.*)/;
+
+  $cmd  = "/prj/muosran/SWIT/tools/bin/mergeEAST2RNC.pl ";
+  $cmd .= "-log $logfile -east $eastfile -out $logfile.tmp -build $buildNbr";
+
+  @lines = $self->{msh}->exec ($cmd);
+  $status = $self->{msh}->status;
+
+  if ($status != 0) {
+    error ("Unable to execute command: $cmd\n" . join ("\n", @lines));
+    return $status;
+  } # if
+
+  $cmd = "scp -q " . RANUSER . "\@" . RANHOST . ":$logfile.tmp $from";
+
+  ($status, @lines) = Execute $cmd;
+
+  $status >>= 8;
+
+  if ($status != 0) {
+    error ("Unable to execute command: $cmd\n" . join ("\n", @lines));
+    return $status;
+  } # if
+
+  $cmd = "rm -f $eastfile $logfile $logfile.tmp";
+
+  ($status, @lines) = $self->{msh}->exec ($cmd);
+  $status = $self->{msh}->status;
+
+  if ($status != 0) {
+    error ("Unable to execute command: $cmd\n" . join ("\n", @lines));
+  } # if
+
+  return $status;
+} # fixUpLogs
+
+############################################################################
+#
+# collectExtendedLogfiles:     Scours an East logfile for extended logfiles
+#                              to collect. Extended logfiles are marked in
+#                              the East logfile.
+#
+# Collection of TM500, NMS and CDR extended logfiles:
+#
+# Look for other logs. Other logs are logs like those produced by TM500 and
+# NMS and CDR. They are noted in the main log file in the format of:
+#
+#      [LOG]
+#      <type> <IP Address> <Logfile>
+#      <type> <IP Address> <Logfile>
+#      ...
+#      [/LOG]
+#
+# Where:
+#
+#      <type>:         TM500|NMS|CDR
+#      <IP Address>    IP address of the machine (why they don't use names...)
+#      <Logfile>       Windows path like:
+#
+#                      C:\TM500\TestLogs\MDL.cmd.2008.04.02-10.24.27.log
+#
+# We need to take the above and formulate an scp command like:
+#
+#      scp -q pswit@<IP Address>:<Logfile> TM500Logs
+#
+# Note that pswit is a generic user and we have previously configured
+# pre-shared ssh access for all users to pswit@<rantm501-rantm507> and
+# <Logfile> has been transformed from "\"'s -> "/"'s because "/"'s also work
+# and work better!
+#
+# Parms:               none
+#
+# Returns:             0 if successful - 1 if unsuccessful
+#
+############################################################################
+sub collectExtendedLogFiles () {
+  my ($self) = @_;
+
+  # Create @tarfiles if it doesn't already exist
+  unless ($self->{tarfiles}) {
+    $self->{tarfiles} = ();
+  } # unless
+
+  my $logpath  = LOGBASE . "/$self->{saveTo}";
+  my $tm500dir = "$logpath/TM500Logs";
+  my $nmsdir   = "$logpath/NMSLogs";
+  my $cdrdir   = "$logpath/CDRLogs";
+
+  my @logLines = $self->getLogFile;
+
+  my $tm500Found       = 0;
+  my $nmsFound         = 0;
+  my $cdrFound         = 0;
+  my $hitlog           = 0;
+
+  foreach (@logLines) {
+    chomp;
+
+    if (/^\[LOG\]/) {
+      $hitlog = 1;
+      next;
+    } elsif (/^\[\/LOG\]/) {
+      $hitlog = 0;
+    } else {
+      if ($hitlog == 1 and /(\S+)\s+(\S+)\s+(\S+)/) {
+       my ($type, $dir, $ip, $logfile);
+
+       if ($1 eq "TM500") {
+         $tm500Found   = 1;
+         $dir          = $tm500dir;
+       } elsif ($1 eq "NMS") {
+         $nmsFound     = 1;
+         $dir          = $nmsdir;
+       } elsif ($1 eq "CDR") {
+         $cdrFound     = 1;
+         $dir          = $cdrdir;
+       } # if
+
+       $type           = $1;
+       $ip             = $2;
+       $logfile        = $3;
+       $logfile        =~ s/\\/\//g;
+
+       unless (-d $dir) {
+         eval { mkpath $dir };
+
+         error "Unable to mkpath $dir\n$@", 1 if $@;
+       } # unless
+
+       # scp is failing for some strange reason for NMS. The
+       # following code is to try to help figure out what's going on
+       # when scp fails.
+
+       # Only do this for NMS
+       if ($type eq "NMS") {
+         # Does the $logfile exist to start with?
+         my $cmd = "ssh pswit\@$ip ls $logfile";
+
+         my ($status, @lines) = Execute $cmd;
+
+         $status >>= 8;
+
+         LogDebug "WARNING: From file, $logfile, does not exist on $ip" if $status != 0;
+       } # if
+
+       my $cmd = "scp -q pswit\@$ip:$logfile $dir";
+
+       my ($status, @lines) = Execute $cmd;
+
+       $status >>= 8;
+
+       if ($type eq "NMS") {
+         if ($status != 0) {
+           LogDebug "Unable to execute $cmd";
+           LogDebug "Lines contains:";
+           LogDebug $_ foreach (@lines);
+
+           my $i = 0;
+
+           do {
+             sleep 1;
+
+             ($status, @lines) = Execute $cmd;
+
+             $status >>= 8;
+             $i++;
+           } until ($status == 0 or $i >= 2);
+         } # if
+       } # if
+
+       error ("Unable to scp logfile $logfile (Errno: $status)\n$cmd\n" . join ("\n", @lines))
+         unless $status == 0;
+      } # if
+    } # if
+  } # foreach
+
+  if ($tm500Found) {
+    push @{$self->{tarfiles}}, {
+      type     => "TM500",
+      tarfile  => "TM500Logs.tgz",
+      path     => $tm500dir,
+      files    => ".",
+    };
+  } # if
+
+  if ($nmsFound) {
+    push @{$self->{tarfiles}}, {
+      type     => "NMS",
+      tarfile  => "NMSLogs.tgz",
+      path             => $nmsdir,
+      files    => ".",
+    };
+  } # if
+
+  if ($cdrFound) {
+    push @{$self->{tarfiles}}, {
+      type     => "CDR",
+      tarfile  => "CDRLogs.tgz",
+      path             => $cdrdir,
+      files    => ".",
+    };
+  } # if
+} # collectExtendedLogFiles
+
+############################################################################
+#
+# createPCScannerLogs: Creates PC Scanner logs  using msh
+#
+# Parms:               none
+#
+# Returns:             0 if successful - 1 if unsuccessful
+#
+############################################################################
+sub createPCScannerLogs ($) {
+  my ($self, $node) = @_;
+
+  my ($status, @lines);
+
+  # Determine how long this test was running
+  my $duration = time - $self->{rantvlStartTime};
+
+  # Kind of an odd algorithim: Compute to the nearest 1/4 hour
+  my $hours    = int ($duration / (60 * 60));
+  my $fractions        = int (($duration % (60 * 60)) / 60);
+
+  if ($fractions < 15) {
+    $fractions = 25;
+  } elsif ($fractions < 30) {
+    $fractions = 5;
+  } elsif ($fractions < 45) {
+    $fractions = 75
+  } else {
+    $fractions = 0;
+    $hours++;
+  } # if
+
+  my $prompt   = uc $node . '.*>';
+  my $timeout  = 30;
+  my $noFiles  = 0;
+
+  verbose_nolf "Collecting PC Scanner logs from the last $hours.$fractions hours...";
+
+  my $cmd = "ssh -t " . RANUSER . "@" . RANHOST. " /prj/muosran/SWIT/moshell/moshell $node";
+  my $msh = new Expect ($cmd);
+
+  error "Unable to start msh", 1 unless $msh;
+
+  $msh->log_user (get_debug);
+
+  $msh->expect (
+    $timeout,
+
+    [ qr "$prompt",
+      sub {
+       debug "Hit prompt!";
+      }
+    ],
+
+    [ timeout =>
+      sub {
+       error "Timed out looking for moshell prompt", 1;
+      }
+    ],
+  );
+
+  $cmd = "pmr -m $hours.$fractions";
+
+  $msh->send ("$cmd\n");
+
+  $msh->expect (
+    $timeout,
+
+    [ qr "Your Choice: " ],
+
+    [ qr "No xml files to parse !",
+      sub {
+       $noFiles = 1;
+      }
+    ],
+
+    [ timeout =>
+      sub {
+       error "Timed out looking for \"Your Choice:\"", 1;
+      }
+    ],
+  );
+
+  if ($noFiles) {
+    verbose " No logs to process - skipping";
+    return -1;
+  } # if
+
+  $cmd = "x";
+
+  $msh->send ("$cmd\n");
+
+  $msh->expect (
+    $timeout,
+
+    [ qr "$prompt" ],
+
+    [ timeout =>
+      sub {
+       error "Timed out looking for moshell prompt", 1;
+      }
+    ],
+  );
+
+  my $proxy_id;
+
+  $cmd = "pst";
+
+  $msh->send ("$cmd\n");
+
+  $msh->expect (
+    $timeout,
+
+    [ qr "$prompt",
+      sub {
+       my $exp = shift;
+
+       my $before = $exp->before;
+
+       if ($before =~ /(\d+).*RNCScanner/) {
+         $proxy_id = $1;
+       } # if
+      }
+    ],
+
+    [ timeout =>
+      sub {
+       error "Timed out looking for moshell prompt", 1;
+      }
+    ],
+  );
+
+  unless ($proxy_id) {
+    error "Unable to find proxy_id";
+    return 1;
+  } # unless
+
+  $cmd = "pbl $proxy_id";
+
+  $msh->send ("$cmd\n");
+
+  $msh->expect (
+    $timeout,
+
+    [ qr "$prompt" ],
+
+    [ timeout =>
+      sub {
+       error "Timed out looking for moshell prompt", 1;
+      }
+    ],
+  );
+
+  return 0;
+} # createPCScannerLogs
+
+############################################################################
+#
+# collectRanTVLLogs:   Collect rantvl logs
+#
+# Parms:               $logPath: Path to logfiles
+#
+# Returns:             0 if successful - 1 if unsuccessful
+#
+############################################################################
+sub collectRanTVLLogs ($) {
+  my ($self, $logpath) = @_;
+
+  return unless ($self->{collectRantvl});
+
+  my ($status, @lines);
+
+  # SIMCQ00007155: We now have unitNbr's like '3m2' which are really
+  # the same machine as as ranrnc03. While ranrnc03m2 is DNS aliased
+  # to ranrnc03, it causes problems because we assume that that will
+  # be the prompt for moshell (see createPCScannerLogs). The following
+  # substr uses only the first character of unitNbr which makes the
+  # assumption that unitNbr 3 (ranrnc03) is the same machine as
+  # unitNbr 3m2 (ranrnc03m2).
+  my $DUTHost  = "ran" . $self->{unitType} . "0" . substr ($self->{unitNbr}, 0, 1);
+
+  if ($self->{unitType} eq "rnc") {
+    # Create PC Scanner logs
+    $status = $self->createPCScannerLogs ($DUTHost);
+
+    unless ($status == 0) {
+      warning "Unable to create PCScannerLogs" if $status > 0;
+    } else {
+      verbose " done";
+
+      # Move files to testlogs
+      my $from = "~" . RANUSER . "/moshell_logfiles/logs_moshell/pmfiles/$DUTHost.gddsi.com/pm";
+      my $to   = "$logpath/PCScannerLogs";
+
+      # Create the remote directory
+      my $cmd = "mkdir -p $to; chmod g+w $to";
+
+      ($status, @lines) = Execute ($cmd);
+
+      $status >>= 8;
+
+      error ("Unable to execute $cmd\n" . join ("\n", @lines), 1)
+       if $status != 0;
+
+      # Copy files
+      $cmd = "scp -qrp " . RANUSER . "@" . RANHOST . ":$from/* $to";
+
+      ($status, @lines) = Execute $cmd;
+
+      $status >>= 8;
+
+      error ("Unable to execute $cmd\n" . join ("\n", @lines), 1)
+       if $status != 0;
+
+      $status = $self->{msh}->exec ("rm -rf $from/*");
+      @lines  = $self->{msh}->lines;
+
+      error ("Unable to execute $cmd\n" . join ("\n", @lines), 1)
+       if $status != 0;
+
+      push @{$self->{tarfiles}}, {
+       type            => "PCScanner",
+       tarfile         => "PCScannerLogs.tgz",
+       path            => $to,
+       files           => ".",
+      };
+    } # if
+  } # if
+
+  my $from     = RANTVL_LOGBASE . "/$self->{saveTo}";
+  my $to       = "$logpath/Rantvl";
+
+  eval { mkpath $to };
+
+  error "Unable to mkpath $to\n$@", 1 if $@;
+
+  # Get any alarms
+  if ($self->{rantvlStartTime}) {
+    use POSIX qw (ceil);
+
+    my $minutes        = ceil ((time - $self->{rantvlStartTime}) / 60);
+    my $DUTHost        = "ran" . $self->{unitType} . "0" . $self->{unitNbr};
+    my $logfile        = $to . (($self->{unitType} eq "rnc") ? "/RNCAlarms.txt" : "/RBSAlarms.txt");
+    my $cmd    = "domsh -v -q -h $DUTHost -m \"lgar ${minutes}m\" > $logfile";
+
+    my ($status, @lines) = Execute $cmd;
+
+    $status >>= 8;
+
+    error ("Unable to execute $cmd\n" . join "\n", @lines) if $status != 0;
+  } # if
+
+  # Copy files
+  my $cmd = "scp -rpq " . RANUSER . "\@" . RANHOST . ":$from/* $to";
+
+  ($status, @lines) = Execute $cmd;
+
+  $status >>= 8;
+
+  return $status if $status;
+
+  verbose_nolf ".";
+
+  # Removed copies
+  $cmd = "ssh " . RANUSER . "\@" . RANHOST . " rm -rf $from";
+
+  ($status, @lines) = Execute $cmd;
+
+  $status >>= 8;
+
+  return $status if $status;
+
+  verbose_nolf ".";
+
+  push @{$self->{tarfiles}}, {
+    type               => "RANTVL",
+    tarfile            => "RANTVLLogs.tgz",
+    path               => $to,
+    files              => ".",
+  };
+
+  return 0;
+} # collectRanTVLLogs
+
+############################################################################
+#
+# collectLogfiles:     Saves the logfiles for an EAST test run
+#
+# Parms:               none
+#
+# Returns:             0 if successful - 1 if unsuccessful
+#
+############################################################################
+sub collectLogFiles (;$$) {
+  my ($self, $testErrors, $checkin_on_error) = @_;
+
+  return 0 unless $self->{collectLogFiles};
+
+  $testErrors       ||= 0;
+  $checkin_on_error ||= 1;
+
+  $self->{saveTo} = "." unless $self->{saveTo};
+
+  my $viewPath = "$ENV{MNT_DIR}/snapshot_views/$self->{userdir}/$self->{view}";
+
+  # Copy relevant logs from
+  my $eastLogBase = "$ENV{MNT_DIR}/$ENV{EAST_REL}/DUT/$self->{unitType}$self->{unitNbr}/data/logs";
+
+  # To here
+  my $logpath = LOGBASE . "/$self->{saveTo}";
+
+  verbose "logpath=$logpath";
+
+  eval { mkpath "$logpath/EASTLogs" };
+
+  error "Unable to mkpath $logpath/EASTLogs\n$@", 1 if $@;
+
+  verbose "Collecting logfiles";
+
+  foreach ("Server_Logs", "regression", "load") {
+    next unless -e "$eastLogBase/$_";
+
+    my $cmd = "cp -rp $eastLogBase/$_ $logpath/EASTLogs";
+
+    my ($status, @lines) = Execute $cmd;
+
+    $status >>= 8;
+
+    error "Unable to copy $eastLogBase/$_ -> $logpath/EASTLogs", 1 if $status != 0;
+  } # foreach
+
+  # We always save EAST logs
+  push @{$self->{tarfiles}}, {
+    type       => "EAST",
+    tarfile    => "EASTLogs.tgz",
+    path       => "$logpath/EASTLogs",
+    files      => ".",
+  };
+
+  my $status = $self->collectRanTVLLogs ($logpath);
+
+  return $status if $status;
+
+  verbose "All logfiles collected";
+
+  # Report logfiles created
+  if (get_verbose) {
+    display "Logfiles created this run:";
+
+    my $cmd = "find " . LOGBASE . "/$self->{saveTo}";
+
+    print $_ foreach (`$cmd`);
+  } # if
+
+  $self->fixUpLogs if $self->{collectRantvl};
+
+  # If we are "run for record" then $self->{testCaseID} should be
+  # set. If not then we're all done and can return.
+  unless ($self->{testCaseID}) {
+    $self->{collectLogFiles} = 0;
+
+    return 0;
+  } # unless
+
+  # if $checkin_on_error is not defined set it to false
+  if ( !defined $checkin_on_error) {
+    $checkin_on_error = "0";
+  } # if
+
+  # check with user to see if they want to check in logs if errors were encountered
+  if ( ( $testErrors > 0 ) && ( $checkin_on_error == 0 ) ) {
+    display_nolf "Errors encountered. Do you still want to check in the log files? (y/n) ";
+      
+    my $response = <STDIN>;
+
+    return 1 unless $response =~ /y/i;
+  } # if
+
+  verbose_nolf "Checking in tar files for run for record"
+    if scalar @{$self->{tarfiles}} > 0;
+
+  foreach (@{$self->{tarfiles}}) {
+    my $to = "$viewPath/vobs";
+
+    if ($$_{type} eq "EAST") {
+      $to .= "/simdev_log";
+    } elsif ($$_{type} eq "TM500") {
+      $to .= "/tm500_log";
+    } elsif ($$_{type} eq "RANTVL" or $$_{type} eq "CDR" or $$_{type} eq "PCScanner") {
+      $to .= "/rantest_build3_log";
+    } elsif ($$_{type} eq "NMS") {
+      $to .= "/nms_sim_log";
+    } else {
+      error "Unknown tarfile type: $$_{type}";
+      next;
+    } # if
+
+    $to .= "/$self->{testCaseID}";
+
+    # Create testcaseID directory if necessary
+    _mkDirElement $to;
+
+    # Will create element if necessary
+    _checkOutElement "$to/$$_{tarfile}";
+
+    # Remove either old tarfile or empty container. We're about to fill it.
+    my ($status, @lines) = Execute "rm -f $to/$$_{tarfile}";
+
+    $status >>= 8;
+
+    error "Unable to remove old tarfile", 1
+      unless $status == 0;
+
+    _makeTar "$to/$$_{tarfile}", $$_{path}, $$_{files};
+
+    # Check in the element
+    _checkInElement "$to/$$_{tarfile}";
+
+    verbose_nolf ".";
+  } # foreach
+
+  verbose " done"
+    if scalar @{$self->{tarfiles}} > 0;
+
+  verbose_nolf "Capturing baselines.";
+
+  # We put baselines into here
+  my $to = "$viewPath/vobs/rantest_build3_log/$self->{testCaseID}/baselines.readme";
+
+  _checkOutElement $to;
+
+  # Remove either old file or empty container. We're about to fill it.
+  my @lines;
+
+  ($status, @lines) = Execute "rm -f $to";
+
+  $status >>= 8;
+
+  error "Unable to remove baseline.readme", 1
+    unless $status == 0;
+
+  $self->makeBaselinesReadme ($to);
+
+  # Check in the element
+  _checkInElement $to;
+
+  verbose " done";
+
+  $self->{collectLogFiles} = 0;
+
+  return 0;
+} # collectLogFiles
+
+1;
+
+=head1 NAME
+
+Nethawk::East - East Object Model module
+
+=head1 VERSION
+
+Version 1.0 - January 17, 2008
+
+=head1 DESCRIPTION
+
+Encapsulates the East Simulator as an object. Methods are provided to
+connect, configure and run tests on an East Simulator.
+
+=head1 SYNOPSIS
+
+use Nethawk::East;
+
+$e = new Nethawk::East;
+
+=head1 METHODS
+
+=head2 new (<parms>)
+
+Construct a new East object. The following OO style arguments are
+supported:
+
+Parameters:
+
+=over
+
+=item host:
+
+Name of host to connect through. Default: raneast
+
+=item username:
+
+Username to connect as. Default $USER
+
+=item password:
+
+Password to use. Default passwordless.
+
+=item debug:
+
+If set then the East object will emit debugging information
+
+=back
+
+=head2 validTestType (type)
+
+Return a status indicating if the passed in test type is valid (and an
+error message if not)
+
+=over
+
+=item testType
+
+Type of test requested
+
+=item Returns
+
+List contains a status (0 = valid test type, 1 = invalid test type)
+and an optional error message.
+
+=back
+
+=head2 inUse ()
+
+Determines if the unit of type type is in use.
+
+=over
+
+=item Returns undef if not in use or an error message if in use
+
+=back
+
+=head2 viewExists (view)
+
+Determines if the view exists on the remote host
+
+=over
+
+=item view
+
+View tag to check
+
+=item Returns
+
+1 if view exists - 0 otherwise
+
+=back
+
+=head2 testExists (type, name)
+
+Determines if the named test exists for that test type
+
+=over
+
+=item type
+
+Specifies what type of test to check
+
+=item name
+
+Specifies the name of the test
+
+=item Returns 1 if test exists - 0 otherwise
+
+=back
+
+=head2 getLogFile ()
+
+Returns the log in an array
+
+=over
+
+=item None
+
+=item Returns
+
+An array of lines from the log file. Note that although EAST logfiles
+are binary, this method first passes the file through strings(1).
+
+=back
+
+=head2 testResult (name)
+
+Checks the test's logfile to determine the result
+
+Parameters:
+
+=over
+
+=item name
+
+Name of test
+
+=item Returns
+
+A status - 0 if we are able to get the results, 1 if we can't - and a
+message of "Success", "Failure", "Incomplete" or an error message
+
+=back
+
+=head2 shell (script, opts)
+
+Execute a shell script returning the results.
+
+Parameters:
+
+=over
+
+=item script
+
+Script to run
+
+=item opts
+
+Additional options passed to script
+
+=item Returns
+
+$status of shell exeuction and @lines of output
+
+=back
+
+=head2 rantvl (cmd)
+
+Start rantvl
+
+Parameters:
+
+=over
+
+=item cmd
+
+Rantvl command to execute
+
+=item Returns
+
+$pid of rantvl process and a message
+
+=back
+
+=head2 rendezvous (phrase, timeout)
+
+Rendezvous with EAST process by searching the log file for a specific
+phrase. We will use $self->{timeout} to determine how long we are
+gonna wait for this phrase to appear. We will divide $self->{timeout}
+by 10, making 10 attempts. So with a default timeout of 180 seconds,
+we will try 10 times 18 seconds apart, for the phrase to appear before
+timing out.
+
+Parameters:
+
+=over
+
+=item phrase
+
+Phrase to search for
+
+=item timeout
+
+How long to time out waiting for the rendezvous
+
+=item Returns
+
+undef if rendezvous was successful - error message otherwise.
+
+=back
+
+=head2 connected ()
+
+Checks to see if you're connected to EAST
+
+Parameters:
+
+=item None
+
+=item Returns
+
+undef if connected - error message otherwise
+
+=back
+
+=head2 connect (view, unitType, unitNbr, tm500, nms)
+
+Connects to the remote East machine
+
+Parameters:
+
+=over
+
+=item view
+
+View name to set to to run the the test
+
+=item unitType
+
+Type of unit (rbs, rnc or east)
+
+=item unitNbr
+
+Number of the unit
+
+=item tm500
+
+Name of tm500 view (if any)
+
+=item nms
+
+Name of nms view (if any)
+
+=item Returns
+
+Undefined if connection was successful or error message if not
+
+=back
+
+=head2 exec (class, name, timeout)
+
+Parameters:
+
+=over
+
+=item class
+
+Specifies which class of test. Must be one of:
+
+ load  LoadTCRunner
+ pool  RegressionLoadRunner
+ tc    RegressionRunner
+ ts    RegressionTSRunner
+
+=item name
+
+Name of the test. Currently this is the filename for the test.
+
+=item timeout
+
+(Optional) Timeout value for this command
+
+=item returns status of remotely executed test.
+
+=back
+
+=head2 disconnect ()
+
+Parameters:
+
+=over
+
+=item none
+
+=back
+
+=head2 setCollectLogFiles (collectLogFiles)
+
+Sets CollectLogFiles to notate that we need to collect log files
+
+Parameters:            
+
+=over
+
+=item collectLogFiles
+
+Boolean indictating where or not to collect log files
+
+=item Returns
+
+Old collectLogFiles setting
+
+=back
+
+=head setTestCaseID
+
+Sets TestCaseID for later use by collectLogFiles
+
+Parameters:
+
+=over
+
+=item TestCaseID
+
+=item Returns
+
+Nothing
+
+=back
+
+=head2 setSaveTo (path)
+
+Sets saveTo for later use by collectLogFiles
+
+Parameters:
+
+=over
+
+=item path
+
+Path to save things to
+
+=item Returns
+
+Nothing
+
+=back
+
+=head2 getSaveTo ()
+
+Gets saveTo
+
+Parameters:
+
+=over
+
+=item None
+
+=item Returns
+
+saveTo path
+
+=back
+
+=head2 getTimeout ()
+
+Returns the timeout value for the remote execution object (if
+connected)
+
+Parameters
+
+=over
+
+=item None
+
+= item Returns
+
+Timeout value for remote execution object, if connected, or undefined.
+
+=head2 collectLogFiles ()
+
+Saves the logfiles for an EAST test run
+
+Parameters:
+
+=over
+
+=item None
+
+=item Returns
+
+0 if successful - 1 if unsuccessful
+
+=back
+
+=head2 setTimeout (timeout)
+
+Sets timeout value for remote execution object for all subsequent
+exec's.
+
+Parameters:
+
+=over
+
+=item timeout
+
+New timeout value
+
+=item Returns
+
+Old timeout value (if previously connected)
+
+=head1 ALSO SEE
+
+None.
+
+=head1 KNOWN DEFECTS
+
+None.
+
+=head1 TODO
+
+=over 4
+
+=item ...
+
+=back
+
+=head1 DEVELOPERS
+
+=over 4
+
+=item Andrew@DeFaria.com (Original Author)
+
+=item Gantry York, gantry.york@gdc4s.com (Maintainer)
+
+=back
+
+=head1 LICENSE & COPYRIGHT
+
+Copyright (c) 2008 General Dynamics, Inc.  All Rights Reserved.
diff --git a/rantest/rantest b/rantest/rantest
new file mode 100644 (file)
index 0000000..bb3f5ca
--- /dev/null
@@ -0,0 +1,1375 @@
+#!/usr/bin/perl\r
+##############################################################################\r
+#\r
+# Name:                rantest\r
+#\r
+# Description: This script is a test driver script capable of running tests\r
+#              individually or from a file. There are many facilities for\r
+#              specifying input and options to this program - see the usage\r
+#              and help subroutines for clues. Basically you can run rantest\r
+#              by itself and it will interactively prompt you for what to do\r
+#              and what information or options it needs. Additionally you can\r
+#              set options in the environment such as RANTEST_VIEW or\r
+#              RANTEST_UNIT to serve as defaults. Or you can use -view or\r
+#              -type, for example, at the command line to supply such parms.\r
+#              If rantest still doesn't have all it needs it will prompts.\r
+#\r
+#              Note that options and/or test cases can be specified in config\r
+#              files specified by RANTEST_FILE or -file. Embedded in the\r
+#              config file can be additional options in the form of:\r
+#\r
+#              b2_l3_rnc_irt_001.test:\r
+#              ----------------------\r
+#              view:   p6258c_SIMCQ00000100_intview\r
+#              type:   rnc\r
+#              unit:   4\r
+#              exec: tc CN_SIM/TC_CN_Simulation_RANAP_Setup.profile -timeout 60\r
+#              exec: tc CN_SIM/TC_CN_Simulation.profile -timeout -1 -pause 5\r
+#              exec: tc RBS_SIM/TC_RBS_Sim_Cell_Setup.profile -timeout 180\r
+#              exec: tc l3_rnc/irt/TC_b2_l3_rnc_irt_001.profile -timeout 180\r
+#\r
+#              Above we've set view, type and unit for the test run\r
+#              and defined test steps of tc\r
+#              CN_SIM/TC_CN_Simulation_RANAP_Setup.profile,\r
+#              CN_SIM/TC_CN_Simulation.profile,\r
+#              RBS_SIM/TC_RBS_Sim_Cell_Setup.profile and\r
+#              l3_rnc/irt/TC_b2_l3_rnc_irt_001.profile\r
+#\r
+#              Note that you can specify optional additional\r
+#              parameters after the test name like -timeout and a\r
+#              bunch of other parameters as described in the "Command\r
+#              Line in East" document.\r
+#\r
+#              This would be run as so:\r
+#\r
+#              $ rantest -file b2_l3_rnc_irt_001.test\r
+#\r
+#              Suite files, those ending in .suite, are different\r
+#              from .test files in that they merely contain a list of\r
+#              .test files (relative to <view>/vobs/simdev/test) to\r
+#              execute for this suite. Note that parameters can be\r
+#              added after the suite file name:\r
+#\r
+#              nightly.suite:\r
+#              --------------\r
+#              # RNC IRT tests\r
+#              b2_l3_rnc_irt_007.test -unit 4\r
+#              b2_l3_rnc_irt_014.test\r
+#\r
+#              # RNC SCH tests\r
+#              b2_l3_rnc_sch_001.test -view official_view\r
+#              b2_l3_rnc_sch_003a.test\r
+#\r
+# Author:      Andrew@ClearSCM.com\r
+#\r
+# Copyright (c) 2008, 2009 General Dynamics\r
+#\r
+# All rights reserved except as subject to DFARS 252.227-7014 of contract\r
+# number CP02H8901N issued under prime contract N00039-04-C-2009.\r
+#\r
+# Warning: This document contains technical data whose export is restricted\r
+# by the Arms Export Control Act (Title 22, U.S.C., Sec 2751, et seq.) or the\r
+# Export Administration Act of 1979, as amended, Title, 50, U.S.C., App. 2401\r
+# et seq. Violations of these export laws are subject to severe criminal\r
+# penalties. Disseminate in accordance with provisions of DoD Directive\r
+# 5230.25.\r
+#\r
+##############################################################################\r
+use strict;\r
+use warnings;\r
+\r
+use File::Basename;\r
+use File::Glob ':glob';\r
+use File::Path;\r
+use FindBin;\r
+use Getopt::Long;\r
+use Net::Domain qw (hostname);\r
+use Term::ANSIColor qw (:constants);\r
+use Term::ReadLine;\r
+use Term::ReadLine::Gnu;\r
+\r
+# Use the SDE Tools libraries. Sorry for this long path. I didn't come\r
+# up with it!\r
+#use lib "/cleartrig/ent/SNSD/muos/ccadm_tools/vobs/ranccadm/scripts/lib";\r
+use lib "$FindBin::Bin/../lib";\r
+\r
+use DateUtils;\r
+use Display;\r
+use Utils;\r
+use GetConfig;\r
+use Logger;\r
+\r
+error "$FindBin::Script only runs on seast1", 1\r
+  unless hostname eq "seast1";\r
+\r
+use lib "$FindBin::Bin/../lib";\r
+\r
+use GD::RantestDB;\r
+use GD::Nethawk::East;\r
+\r
+use constant VERSION_NBR=> "1.2.5";\r
+use constant VERSION   => BOLD GREEN   VERSION_NBR;\r
+use constant PROMPT    => BOLD YELLOW  "$FindBin::Script>";\r
+use constant DESC      => BOLD RED     "$FindBin::Script",\r
+                          RESET        "Version", VERSION\r
+                         . RESET       ": "\r
+                         . BOLD CYAN   "RAN T"\r
+                         . RESET       "ool for "\r
+                         . BOLD CYAN   "E"\r
+                        . RESET        "xecution of "\r
+                        . BOLD CYAN    "S"\r
+                        . RESET        "ystem "\r
+                        . BOLD CYAN    "T"\r
+                        . RESET        "ests";\r
+\r
+use constant HISTORY_FILE => "$ENV{HOME}/.rantest_hist";\r
+use constant LOGBASE     => "$ENV{MNT_DIR}/testlogs";\r
+\r
+############################################################################\r
+# Globals\r
+############################################################################\r
+my $_east;\r
+my $_connected         = 0;\r
+my $_debugging;\r
+my $_log;\r
+my $_term;\r
+my $_rantestdb;\r
+my $_testNbr           = 0;\r
+my $_validationNbr     = 0;\r
+\r
+my %_stats;\r
+\r
+my %_executionResults;\r
+\r
+my (%_opts, %_cmdline_opts);\r
+\r
+# Seed opts from the environment.\r
+$_opts{eastview}       = $ENV{RANTEST_EASTVIEW}        if $ENV{RANTEST_EASTVIEW};\r
+$_opts{eastview}       = $ENV{RANTEST_VIEW}            if $ENV{RANTEST_VIEW} && !$_opts{eastview};\r
+$_opts{tm500view}      = $ENV{RANTEST_TM500VIEW}       if $ENV{RANTEST_TM500VIEW};\r
+$_opts{nmsview}                = $ENV{RANTEST_NMSVIEW}         if $ENV{RANTEST_NMSVIEW};\r
+$_opts{type}           = $ENV{RANTEST_TYPE}            if $ENV{RANTEST_TYPE};\r
+$_opts{class}          = $ENV{RANTEST_CLASS}           if $ENV{RANTEST_CLASS};\r
+$_opts{unit}           = $ENV{RANTEST_UNIT}            if $ENV{RANTEST_UNIT};\r
+$_opts{test}           = $ENV{RANTEST_TEST}            if $ENV{RANTEST_TEST};\r
+$_opts{file}           = $ENV{RANTEST_FILE}            if $ENV{RANTEST_FILE};\r
+$_opts{rfr}            = $ENV{RANTEST_RFR}             if $ENV{RANTEST_RFR};\r
+$_opts{checkin_on_error}= $ENV{CHECKIN_ON_ERROR}        if $ENV{CHECKIN_ON_ERROR};\r
+$_opts{feature}                = $ENV{RANTEST_FEATURE}         if $ENV{RANTEST_FEATURE};\r
+$_opts{regression}     = $ENV{RANTEST_REGRESSION}      if $ENV{RANTEST_REGRESSION};\r
+$_opts{secure}         = $ENV{RANTEST_SECURE}          if $ENV{RANTEST_SECURE};\r
+\r
+# Explicitly default secure to either $ENV{RANTEST_SECURE}, if defined, otherwise 1\r
+$_opts{secure} = $ENV{RANTEST_SECURE} ? $ENV{RANTEST_SECURE} : 1;\r
+\r
+sub usage (;$) {\r
+  my ($msg) = @_;\r
+\r
+  my $usage = "ERROR: $msg\n\n" if $msg;\r
+\r
+  $usage .= <<END;\r
+Usage: $FindBin::Script\t[-us|age] [-v|erbose] [-d|ebug]\r
+  [-view|-eastview <view>] [-tm500view <view>] [-nmsview <view>]\r
+  [-type <type>] [-class <class>] [-rfr <testcase ID>] [-checkin_on_error]\r
+  [-unit <unit #>] [-test <test>] [-file <file>] [-feature <feature>]\r
+  [-[no]s|ecure]\r
+\r
+Where:\r
+\r
+  -us|age:          Display usage\r
+  -ve|rbose:        Turn on verbose mode\r
+  -d|ebug:          Turn on debug mode\r
+  -[ea|st]view <tag> View tag to run test(s) under\r
+  -tm|500view <tag>  TM 500 view to set into the environment for\r
+                    test\r
+  -nm|sview <tag>    NMS view to set into the environment for\r
+                    test\r
+  -ty|pe <type>      Type of unit to test (i.e. rbs | rnc)\r
+  -c|lass <class>    Class of test (i.e. one of the following):\r
+\r
+      Load          LoadTCRunner\r
+      Pool          RegressionLoadRunner\r
+      TC            RegressionRunner\r
+      TS            RegressionTSRunner\r
+      Manual        Manual test\r
+\r
+  -un|it <unit #>    Unit number to test on\r
+  -te|st <test>      Name of test (Optional: If not specific you will\r
+                     be prompted for test case names)\r
+  -f|ile <file>      File containing a list of tests to execute (Optional:\r
+                     Contains a file of test classes and names to test)\r
+  -rfr <testcase ID> Run for record: ID is the test case ID to store\r
+                    results under\r
+  -checkin_on_error  Checks in rfr log files even if a test(s) fails\r
+  -regression       Run regression tests. These tests will log there\r
+                    results.\r
+  -feature <feature> If specified then FEATURE will be set into the\r
+                    environment on the blade before calling\r
+                    start_east_auto.\r
+  -[no]s|ecure      Indicates whether or not to secure the node before\r
+                    attempting to connect to it. (Default: secure).\r
+\r
+NOTE: Set ANSI_COLORS_DISABLED to turn off colors\r
+\r
+If you specify -file you cannot specify -test nor -class. -class'es are\r
+read from -file.\r
+\r
+Additionally, options above can be specified in the environment by\r
+preceeding the environment variable with \"RANTEST_\". For example,\r
+\r
+  \$ export RANTEST_TYPE=rbs\r
+  \$ export RANTEST_UNIT=2\r
+  \$ rantest\r
+\r
+Also such options can be specified in the -file:\r
+\r
+  unit: 5\r
+  executionlevel: 2\r
+  ts ts1.profile -timeout -1 -pause 5\r
+  ts ts2.profile -displaylevel 3\r
+  ts ts3.profile -activecalls 10\r
+\r
+Options after test profile name are passed directory to EAST's command\r
+line. The exceptions are -timeout and -pause:\r
+\r
+  -timeout <n>         Specifies the timeout for this test's execution.\r
+                       If negative the test will be placed in the\r
+                       background. No result is recovered from\r
+                       background tests nor are any logfiles analysed\r
+                       or stored. If positive then this sets the\r
+                       timeout period for this test in seconds.\r
+\r
+  -pause <n>           Used in conjunction with -timeout. If test is\r
+                       backgrounded then $FindBin::Script will wait\r
+                       pause seconds before returning control from\r
+                       this test. This allows the backgrounded test\r
+                       time to start.\r
+\r
+  -name <name>         Names a test. Used in conditional execution.\r
+\r
+  -if (<name> <status>)        Run this test if the named test returned <status>\r
+                       where <status> is one of\r
+\r
+                         . Success\r
+                         . Failure\r
+                         . In Progress\r
+                         . Timed out\r
+                         . Failed to execute\r
+                         . Rendezvous\r
+                         . Failed to rendezvous\r
+\r
+Note: Required options not supplied will be prompted for.\r
+END\r
+\r
+  my $pager = $ENV{PAGER} ? $ENV{PAGER} : "more";\r
+\r
+  system "echo \"$usage\" | $pager";\r
+\r
+  exit 1 if $msg;\r
+} # usage\r
+\r
+sub help () {\r
+  display DESC . RESET;\r
+  display <<END;\r
+\r
+Valid commands are:\r
+\r
+help:                  This display\r
+usage:                 Displays command line usage\r
+version:               Displays version of $FindBin::Script\r
+exit|quit:             Exits $FindBin::Script\r
+source <file>          Execute the contents of <file>\r
+set <option>=<value>   Set <option> to <value>\r
+get <option>           Displays <option> (if set)\r
+elock <pattern>                Display elock status (default all units)\r
+\r
+Running tests:\r
+\r
+load <test profile> <options> Run a test case by profile (LoadTCRunner)\r
+pool <test profile> <options> Run a regression load (RegressionLoadRunner)\r
+tc   <test profile> <options> Run a regression (RegressionRunner)\r
+ts   <test profile> <options> Run a regression test suite (RegressionTSRunner)\r
+manual\r
+\r
+Note: ReadLine is supported thus you can edit previous commands.\r
+Try the up arrow!\r
+END\r
+} # Help\r
+\r
+sub getParm ($) {\r
+  my ($prompt) = @_;\r
+\r
+  my $value;\r
+\r
+  while (!$value or $value eq "") {\r
+    display_nolf BOLD YELLOW . "$FindBin::Script needs the following parameter - $prompt" . RESET;\r
+\r
+    $value = <STDIN>;\r
+\r
+    chomp $value;\r
+  } # while\r
+\r
+  return $value;\r
+} # getParm\r
+\r
+sub eLock (;$) {\r
+  my ($unit) = @_;\r
+\r
+  my ($status, @locks) = Execute "ls $ENV{MNT_DIR}/$ENV{EAST_REL}/DUT/*/desktop.lock 2> /dev/null";\r
+\r
+  $status >>= 8;\r
+\r
+  foreach (@locks) {\r
+    my $unit_found;\r
+\r
+    if (/.*DUT\/(\w+)\/desktop/) {\r
+      $unit_found = $1;\r
+\r
+      next if $unit && $unit_found !~ /$unit/i;\r
+    } # if\r
+\r
+    my @fields = split /\//, $_;\r
+    my $uid    = (stat $_)[4];\r
+    my $mtime  = (stat $_)[9];\r
+    my $userid = (getpwuid ($uid))[0];\r
+    my $name   = (getpwuid ($uid))[6];\r
+\r
+    display BOLD CYAN  "$fields[5]\t"\r
+          . RESET      "locked since "\r
+          . BOLD YELLOW        localtime ((stat $_)[9])\r
+          . RESET      " by "\r
+          . MAGENTA    $name\r
+          . RESET      " ("\r
+          . GREEN      $userid\r
+         . RESET       ")";\r
+  } # foreach\r
+} # eLock\r
+\r
+sub displaySummary () {\r
+  my $msg = "Summary:";\r
+\r
+  foreach (sort keys %_stats) {\r
+    $msg .= " $_stats{$_} $_";\r
+  } # foreach\r
+\r
+  $_log->msg ($msg) if $_log;\r
+} # displaySummary\r
+\r
+sub announceTestrun ($) {\r
+  my ($testname) = @_;\r
+\r
+  my $user = $ENV{USER}                ? $ENV{USER}\r
+           : $ENV{LOGNAME}     ? $ENV{LOGNAME}\r
+          : "Unknown";\r
+  my $timestamp = YMDHMS;\r
+\r
+  $_testNbr++;\r
+\r
+  verbose BOLD YELLOW  "Test #" . $_testNbr . "\t"\r
+        . RESET CYAN   $testname\r
+        . RESET                " run on $timestamp by "\r
+        . YELLOW       $user\r
+        . RESET                " on "\r
+        . MAGENTA      $_opts{type}\r
+        . RESET                " unit "\r
+        . CYAN         $_opts{unit}\r
+        . RESET;\r
+\r
+  $_log->log ("Test #$_testNbr $testname run on: $timestamp by $user on $_opts{type} unit $_opts{unit}");\r
+} # announceTestrun\r
+\r
+sub saveHistory {\r
+  $_term->WriteHistory (HISTORY_FILE) if $_term;\r
+} # saveHistory\r
+\r
+sub executeTestStep () {\r
+  if (!$_connected) {\r
+    # Get required parameters if not specified in the command line or environment\r
+    $_opts{eastview}   = getParm "View:"  unless $_opts{eastview};\r
+    $_opts{type}       = getParm "Type:"  unless $_opts{type};\r
+    $_opts{class}      = getParm "Class:" unless $_opts{class};\r
+    $_opts{unit}       = getParm "Unit:"  unless $_opts{unit};\r
+\r
+    # Connect to it\r
+    my $msg = $_east->connect (\r
+      $_opts{eastview},\r
+      $_opts{type},\r
+      $_opts{unit},\r
+      $_opts{tm500view},\r
+      $_opts{nmsview},\r
+      $_opts{feature},\r
+      $_opts{secure}\r
+    );\r
+\r
+    if ($msg) {\r
+      $_log->err ("Unable to connect to EAST\n$msg");\r
+      verbose RED "Skipping renaming test steps" . RESET;\r
+      $_stats{Failed}++;\r
+      return -1;\r
+    } else {\r
+      $_connected = 1;\r
+    } # if\r
+  } # if\r
+\r
+  my ($status, $msg) = Nethawk::East::validTestType ($_opts{class});\r
+\r
+  if ($status != 0) {\r
+    $_log->err ($msg);\r
+    return $status;\r
+  } # if\r
+\r
+  if (!$_opts{test}) {\r
+    # Manual tests only have classes\r
+    unless ($_opts{class} eq 'manual') {\r
+      $_log->err ("No test specified");\r
+      return 1;\r
+    } # unless\r
+  } # if\r
+\r
+  $_east->setCollectLogFiles (1);\r
+\r
+  verbose_nolf $_stats{Run} . ": " . CYAN "$_opts{class}\t" . BOLD YELLOW $_opts{test} . RESET;\r
+\r
+  my $testStepResult;\r
+\r
+  my $startTime        = time;\r
+  my $stepName = "Unknown";\r
+\r
+  if ($_opts{test} =~ /(.*)\.profile/) {\r
+    $stepName = $1;\r
+  } elsif ($_opts{class} eq "manual") {\r
+    $stepName = "Manual";\r
+  } elsif ($_opts{class} eq "shell") {\r
+    $stepName = $_opts{test};\r
+  } elsif ($_opts{test} =~ /^rantvl/) {\r
+    $stepName = $_opts{test};\r
+\r
+    $_east->setRantvlStartTime (time);\r
+  } # if\r
+\r
+  my ($stepID, $errMsg) = $_rantestdb->startSteprun ($stepName);\r
+\r
+  ($status, $testStepResult) = $_east->exec (\%_opts, \%_executionResults);\r
+\r
+  # Collect any extended logs\r
+  if ((!defined $_opts{timeout} or $_opts{timeout} > 0) and\r
+      ($_east->{class} eq "load" or\r
+       $_east->{class} eq "tc"   or\r
+       $_east->{class} eq "ts"   or\r
+       $_east->{class} eq "pool")) {\r
+    $_east->collectExtendedLogFiles;\r
+  } # if\r
+\r
+  my $endTime = time;\r
+\r
+  if ($status == 0) {\r
+    if ($testStepResult eq "Success") {\r
+      verbose GREEN " $testStepResult" . RESET;\r
+      $_stats{Passed}++;\r
+    } elsif ($testStepResult eq "In progress" or\r
+            $testStepResult eq "Logging started") {\r
+      verbose MAGENTA " $testStepResult" . RESET;\r
+      $_stats{Concurrent}++;\r
+    } elsif ($testStepResult eq "Rendezvous") {\r
+      verbose BOLD . " $testStepResult" . RESET;\r
+      $_stats{Rendezvous}++;\r
+    } else {\r
+      verbose RED " $testStepResult" . RESET;\r
+      $status = 1;\r
+      $_stats{Failed}++;\r
+    } # if\r
+  } else {\r
+    if ($testStepResult eq "Skipped") {\r
+      verbose BOLD . " $testStepResult" . RESET;\r
+      $_stats{Skipped}++;\r
+    } elsif ($testStepResult eq "Timed out") {\r
+      verbose CYAN " $testStepResult" . RESET;\r
+      $status = 1;\r
+      $_stats{Timedout}++;\r
+    } else {\r
+      verbose RED " $testStepResult" . RESET;\r
+      $status = 1;\r
+      $_stats{Failed}++;\r
+    } # if\r
+  } # if\r
+\r
+  # Log test step result\r
+  $_log->log ("$_stats{Run}: $_opts{class}\t$_opts{test} $testStepResult");\r
+\r
+  my ($dbErrNbr, $dbErrMsg) = $_rantestdb->endSteprun (\r
+    runID      => $_east->{runID},\r
+    stepID     => $stepID,\r
+    start      => UnixDatetime2SQLDatetime (scalar (localtime ($startTime))),\r
+    end                => UnixDatetime2SQLDatetime (scalar (localtime ($endTime))),\r
+    result     => $testStepResult,\r
+  );\r
+\r
+  error $dbErrMsg if $dbErrNbr != 0;\r
+\r
+  return $status;\r
+} # executeTestStep\r
+\r
+sub testTimeout {\r
+  error "Test timed out ($_opts{testtimeout}) seconds passed)";\r
+\r
+  $_east->disconnect;\r
+\r
+  # Collect logfiles\r
+  $_east->collectLogFiles;\r
+} # testTimeout\r
+\r
+sub interrupted {\r
+  use Term::ReadKey;\r
+\r
+  display BLUE "\nInterrupted" . RESET;\r
+\r
+  displaySummary;\r
+\r
+  display_nolf\r
+    CYAN       . BOLD "C" . RESET CYAN         "ontinue"       . RESET . " or " .\r
+    MAGENTA    . BOLD "A" . RESET MAGENTA      "bort run"      . RESET . " (" .\r
+    CYAN       . BOLD "C" . RESET "/" .\r
+    MAGENTA    . BOLD "a" . RESET ")?";\r
+\r
+  ReadMode ("cbreak");\r
+  my $answer = ReadKey (0);\r
+  ReadMode ("normal");\r
+\r
+  if ($answer eq "\n") {\r
+    display "c";\r
+  } else {\r
+    display $answer;\r
+  } # if\r
+\r
+  $answer = lc $answer;\r
+\r
+  if ($answer eq "c") {\r
+    display "Continuing...";\r
+  } elsif ($answer eq "a") {\r
+    display RED "Aborting run" . RESET;\r
+    $_east->setCollectLogFiles (0);\r
+    saveHistory;\r
+    exit;\r
+  } # if\r
+} # interrupted\r
+\r
+sub interpolate ($) {\r
+  my ($str) = @_;\r
+\r
+  # Perform psuedo variable interpolation. The following psuedo\r
+  # variables are supported:\r
+  #\r
+  # view:        Absolute path to your view\r
+  my $view     = "$ENV{MNT_DIR}/snapshot_views/$_east->{userdir}/$_east->{view}";\r
+  my $simdev   = "$view/vobs/simdev";\r
+\r
+  # msgdefs:     Absolute path to msgdefs\r
+  my $msgdefs  = "$simdev/msgdefs";\r
+\r
+  # validation:  Absolute path to validation\r
+  my $validation       = "$simdev/validation";\r
+\r
+  # logpath:     Absolute path into the "testlogs" area where\r
+  #              logfiles are written\r
+  my $logpath  = LOGBASE . "/" . $_east->getSaveTo;\r
+\r
+  while ($str =~ /\$/) {\r
+    my ($var, $slice);\r
+\r
+    if ($str =~ /\$(\w+)/) {   \r
+      # Regular $var\r
+      $var     = $1;\r
+    } elsif ($str =~ /\$(\[.+?\])\[(.+?)\]/) {\r
+      # A $[fileset][slice] reference\r
+      $var     = $1;\r
+      $slice   = $2;\r
+    } elsif ($str =~ /\$(\[.+?\])/) {  \r
+      # A $[fileset] reference\r
+      $var     = $1;\r
+    } # if\r
+\r
+    if ($var eq "logpath") {\r
+      $str =~ s/\$$var/$logpath/;\r
+    } elsif ($var eq "msgdefs") {\r
+      $str =~ s/\$$var/$msgdefs/;\r
+    } elsif ($var eq "validation") {\r
+      $str =~ s/\$$var/$validation/;\r
+    } elsif ($var eq "view") {\r
+      $str =~ s/\$$var/$view/;\r
+    } elsif ($var =~ /\[(.+)\]/) {\r
+      my $fileset = $1;\r
+\r
+      my @fileset = glob $fileset;\r
+      my $list;\r
+\r
+      if (defined $slice) {\r
+       $fileset = quotemeta $fileset;\r
+\r
+       # Here we handle a slice, but if the slice is of the form x..y\r
+       # then we need to handled it differently\r
+       if ($slice =~ /(\d+)\.\.(\d+)/) {\r
+         # Need to turn off warnings for this next construct of\r
+         # @array[$1..$2]. Otherwise it complains. If we use\r
+         # $array[$1..$2] then it doesn't work! Also take on the\r
+         # base fileset defined above.\r
+         #\r
+         # Adjust bounds\r
+         $2 = $#fileset if $2 > $#fileset;\r
+\r
+         no warnings;\r
+         $list = join ",", @fileset[$1..$2];\r
+         use warnings;\r
+       } else {\r
+         # Not a slice really but an array reference\r
+         $list = "$fileset[$slice]";\r
+       } # if\r
+\r
+       $str =~ s/\$\[$fileset\]\[$slice\]/$list/;\r
+      } else {\r
+       $list = join ",", @fileset;\r
+       $str =~ s/\$\[$fileset\]/$list/;\r
+      } # if\r
+\r
+      if (defined $slice) {\r
+       $str =~ s/\$\[$fileset\]\[$slice\]//;\r
+      } else {\r
+       $str =~ s/\$\[$fileset\]//;\r
+      } # if\r
+    } else {\r
+      error "Unknown variable ($var) encountered in val line:\n$str", 1;\r
+    } # if\r
+  } # while\r
+\r
+  return $str;\r
+} # interpolate\r
+\r
+sub runValidation ($$) {\r
+  my ($cmd, $logfile) = @_;\r
+\r
+  my $origCmd = $cmd;\r
+\r
+  my ($stepID, $errMsg);\r
+\r
+  ($stepID, $errMsg) = $_rantestdb->startSteprun ($origCmd);\r
+\r
+  if ($stepID == 0) {\r
+    error "Unable to startSteprun\n$errMsg";\r
+    return 1;\r
+  } # if\r
+\r
+  my $startTime = time;\r
+\r
+  $cmd = interpolate ($cmd);\r
+\r
+  my ($status, @lines) = Execute ("$cmd >> $logfile 2>&1");\r
+\r
+  $status >>= 8;\r
+\r
+  my $endTime = time;\r
+\r
+  my ($dbErrNbr, $dbErrMsg) = $_rantestdb->endSteprun (\r
+    runID      => $_east->{runID},\r
+    stepID     => $stepID,\r
+    start      => UnixDatetime2SQLDatetime (scalar (localtime ($startTime))),\r
+    end                => UnixDatetime2SQLDatetime (scalar (localtime ($endTime))),\r
+    result     => $status ? "Failure" : "Success",\r
+  );\r
+\r
+  error $dbErrMsg if $dbErrNbr != 0;\r
+\r
+  # Output lines to stdout\r
+  if (-e $logfile) {\r
+    verbose "$_\n" foreach (ReadFile $logfile);\r
+  } else {\r
+    verbose "Unable to read $logfile";\r
+    $status++;\r
+  } # if\r
+\r
+  $_validationNbr++;\r
+\r
+  verbose BOLD YELLOW . "Test #$_testNbr validation #$_validationNbr \t" . RESET CYAN $origCmd\r
+    . (($status == 0) ? GREEN " Success" : RED " Failure") . RESET;\r
+\r
+  $_log->log ("Test #$_testNbr validation #$_validationNbr\t$origCmd " . (($status == 0) ? "Success" : "Failure"));\r
+\r
+  return $status;\r
+} # runValidation\r
+\r
+sub runValidations (@) {\r
+  my @validations = @_;\r
+\r
+  my $validationErrs = 0;\r
+\r
+  # Make Validation log directory\r
+  my $validationDir = LOGBASE . "/" . $_east->getSaveTo . "/Validations";\r
+\r
+  eval { mkpath $validationDir };\r
+\r
+  error "Unable to create Validation directory - $validationDir\n$@", 1 if $@;\r
+\r
+  chmod 0775, $validationDir;\r
+\r
+  my $viewPath = "$ENV{MNT_DIR}/snapshot_views/$_east->{userdir}/$_east->{view}";\r
+  my $vobPath  = "vobs/simdev";\r
+\r
+  foreach (@validations) {\r
+    my @tokens = split;\r
+    my $outfile        = $tokens[0] . ".log";\r
+\r
+    $validationErrs += runValidation $_, "$validationDir/$outfile";\r
+  } # foreach\r
+\r
+  $_stats{Failed} += $validationErrs;\r
+\r
+  return $validationErrs;\r
+} # runValidations\r
+\r
+sub runTestFile ($) {\r
+  my ($file) = @_;\r
+\r
+  my $testName = fileparse $file, ".test";\r
+\r
+  my $testID = $_rantestdb->startTest ($testName);\r
+\r
+  my %fileopts = GetConfig ($file);\r
+\r
+  # GetConfig leaves keys %fileopts as case sensitive but we want them\r
+  # case insentive so fix that here.\r
+  foreach (keys (%fileopts)) {\r
+    my $key = lc $_;\r
+\r
+    # Make "view" an alias for "eastview" but only if there is no\r
+    # eastview already defined\r
+    if ($key eq "view") {\r
+      if (!$fileopts{eastview}) {\r
+       $fileopts{eastview} = delete $fileopts{view};\r
+       $_ = $key = "eastview";\r
+      } # if\r
+    } # if\r
+\r
+    # Set into %_opts only if that key doesn't exist already. This\r
+    # allows command line options to override options specified in the\r
+    # file. The exception to this is the exec array. This gets\r
+    # replaced in suite runs.\r
+    if ($key eq "exec" || !$_opts{$key}) {\r
+      $_opts{$key} = $fileopts{$_};\r
+    } # if\r
+  } # foreach\r
+\r
+  my $testStartTime = time;\r
+\r
+  $_east->setSaveTo ("$testName/$_opts{type}$_opts{unit}/" . YMDHMS ($testStartTime));\r
+\r
+  eval { mkpath LOGBASE . "/" . $_east->getSaveTo };\r
+\r
+  error "Unable to create log directory\n$@", 1 if $@;\r
+\r
+  chmod 0775, LOGBASE . "/" . $_east->getSaveTo;\r
+\r
+  unless ($_log) {\r
+    $_log = new Logger (\r
+      name     => $testName,\r
+      path     => LOGBASE . "/" . $_east->getSaveTo,\r
+      append   => "yes",\r
+    );\r
+\r
+    $_log->log ("$FindBin::Script Version " . VERSION_NBR . "\nUsing view: $_opts{eastview}");\r
+  } # unless\r
+\r
+  verbose BOLD CYAN "Using view: " . RESET $_opts{eastview};\r
+\r
+  announceTestrun $testName;\r
+\r
+  my @tokens;\r
+\r
+  my $testFailures     = 0;\r
+  my $result           = 0;\r
+  my $errMsg;\r
+\r
+  # Set testTimer if specified\r
+  if ($_opts{testtimeout}) {\r
+    $SIG{ALRM} = \&testTimeout;\r
+    alarm $_opts{testtimeout};\r
+  } # if\r
+\r
+  ($_east->{runID}, $errMsg) = $_rantestdb->startTestrun (UnixDatetime2SQLDatetime localtime $testStartTime);\r
+\r
+  return ($_east->{runID}, $errMsg) if $_east->{runID} == 0;\r
+\r
+  $_validationNbr = 0;\r
+\r
+  if (ref $_opts{exec} eq "ARRAY") {\r
+    foreach (@{$_opts{exec}}) {\r
+      @tokens = split;\r
+\r
+      $_opts{class} = shift @tokens;\r
+      $_opts{test}  = join " ", @tokens;\r
+\r
+      $_stats{Run}++;\r
+\r
+      $result = executeTestStep;\r
+\r
+      if ($result == -1) {\r
+       $testFailures++;\r
+       last;\r
+      } else {\r
+       $testFailures += $result;\r
+      } # if\r
+    } # foreach\r
+  } else {\r
+    if ($_opts{exec}) {\r
+      @tokens = split /\s+/, $_opts{exec};\r
+\r
+      $_opts{class} = shift @tokens;\r
+      $_opts{test}  = join " ", @tokens;\r
+\r
+      $_stats{Run}++;\r
+\r
+      $result = executeTestStep;\r
+\r
+      if ($result == -1) {\r
+       $testFailures++;\r
+      } else {\r
+       $testFailures += $result;\r
+      } # if\r
+    } # if\r
+  } # if\r
+\r
+  my $execType = $_opts{rfr}           ? "Run for Record"\r
+               : $_opts{regression}    ? "Regression" : "Normal";\r
+\r
+  return 1 if $result == -1;\r
+\r
+  # Disconnect from EAST\r
+  $_east->disconnect;\r
+\r
+  # Assign 'Failed' and 'Timedout' 0 if they are not initialized\r
+  $_stats{Failed}   ||= 0;\r
+  $_stats{Timedout} ||= 0;\r
+\r
+  my $testErrors = $_stats{Failed} + $_stats{Timedout};\r
+\r
+  # Collect log files and check them in based on checkin_on_error option\r
+  $_east->collectLogFiles($testErrors, $_opts{checkin_on_error});\r
+\r
+  if ($testFailures == 0 and $_opts{val}) {\r
+    my @validations = ref $_opts{val} eq "ARRAY"\r
+                    ? @{$_opts{val}}\r
+                   : ($_opts{val});\r
+\r
+    $testFailures += runValidations @validations;\r
+  } # if\r
+\r
+  # Log test results\r
+  verbose BOLD YELLOW . "Test #$_testNbr\t" . RESET CYAN $testName\r
+    . (($testFailures == 0) ? GREEN " Success" : RED " Failure") . RESET;\r
+\r
+  $_log->log ("Test #$_testNbr\t$testName " . (($testFailures == 0) ? "Success" : "Failure"));\r
+\r
+  my ($_runID, $dbErrMsg) = $_rantestdb->endTestrun (\r
+    runID              => $_east->{runID},\r
+    suiteID            => $_east->{suiteID} ? $_east->{suiteID} : 0,\r
+    name               => fileparse ($file, ".test"),\r
+    execType           => $execType,\r
+    start              => UnixDatetime2SQLDatetime (scalar (localtime ($testStartTime))),\r
+    result             => $testFailures == 0 ? "Success" : "Failure",\r
+    unit               => "$_east->{unitType}$_east->{unitNbr}",\r
+    rantest_version    => VERSION_NBR,\r
+    east_version       => $ENV{EAST_REL},\r
+    ran_version                => $_east->{ran_version},\r
+    tm500_version      => $_east->{tm500_version},\r
+    nms_version                => $_east->{nms_version},\r
+    eastlogs           => LOGBASE . "/" . $_east->getSaveTo,\r
+  );\r
+\r
+  error $dbErrMsg if $_runID == 0;\r
+\r
+  return $testFailures;\r
+} # runTestFile\r
+\r
+sub setPath ($) {\r
+  my ($view) = @_;\r
+\r
+  return if $ENV{PATH} =~ /$view/;\r
+\r
+  my $userdir;\r
+\r
+  if ($view =~ /(\S+)_SIM/) {\r
+    $userdir = $1;\r
+  } else {\r
+    error "Unable to find userdir", 1;\r
+  } # if\r
+\r
+  my @paths = (\r
+    "$ENV{MNT_DIR}/snapshot_views/$userdir/$view/vobs/simdev/sbin",\r
+    "$ENV{MNT_DIR}/snapshot_views/$userdir/$view/vobs/simdev/bin",\r
+    "$ENV{MNT_DIR}/snapshot_views/$userdir/$view/vobs/gdtools/rantest_auto/bin",\r
+  );\r
+\r
+  $ENV{PATH} = join (":", @paths) . ":" . $ENV{PATH};\r
+} # setPath\r
+\r
+sub runSuiteFile ($) {\r
+  my ($file) = @_;\r
+\r
+  error "View must be specified when running in suite mode", 1 unless $_opts{eastview};\r
+\r
+  setPath $_opts{eastview};\r
+\r
+  my $userdir;\r
+\r
+  if ($_opts{eastview} =~ /(\S+)_SIM/) {\r
+    $userdir = $1;\r
+  } else {\r
+    error "Unable to find userdir", 1;\r
+  } # if\r
+\r
+  unless (open FILE, $file) {\r
+    error "Unable to open file $file - $!";\r
+    return 1\r
+  } # unless\r
+\r
+  my @lines = <FILE>;\r
+\r
+  chomp @lines;\r
+\r
+  close FILE;\r
+\r
+  my $i                        = 0;\r
+  my $suiteStartTime   = time;\r
+  my $suiteFailures    = 0;\r
+  my $suiteName                = fileparse ($file, ".suite");\r
+\r
+  $_log = new Logger (\r
+    name       => $suiteName,\r
+    path       => LOGBASE,\r
+    append     => "yes",\r
+  );\r
+\r
+  $_log->log ("$FindBin::Script Version " . VERSION_NBR);\r
+\r
+  ($_east->{suiteID}) = $_rantestdb->startSuiterun ($suiteName);\r
+\r
+  verbose BOLD MAGENTA "Suite\t" . RESET GREEN $suiteName . RESET;\r
+\r
+  $_log->log ("Suite\t$suiteName");\r
+\r
+  foreach (@lines) {\r
+    $i++;\r
+    next if /(^#|^$)/;\r
+\r
+    my @components = split;\r
+\r
+\r
+    my $viewPath = "$ENV{MNT_DIR}/snapshot_views/$userdir/$_opts{eastview}/vobs/simdev/test/";\r
+    my $testFile = "$viewPath/" . shift @components;\r
+\r
+    unless (-e $testFile) {\r
+      error "Unable to find test file $testFile (Line: $i)";\r
+      next;\r
+    } # unless\r
+\r
+    unless (/\.test/) {\r
+      error "Not a .test file: $testFile (Line: $i)";\r
+      next;\r
+    } # unless\r
+\r
+    # Get test options. It seems GetOptions doesn't support taking\r
+    # input from anything but @ARGV so we'll have to save a copy and\r
+    # restore it.\r
+    my @savedOptions = @ARGV;\r
+\r
+    @ARGV = split;\r
+\r
+    my %suiteOptions;\r
+\r
+    my $status = GetOptions (\r
+      \%suiteOptions,\r
+      "eastview=s",\r
+      "tm500view=s",\r
+      "nmsview=s",\r
+      "type=s",\r
+      "class=s",\r
+      "unit=s",\r
+      "test=s",\r
+      "file=s",\r
+      "rfr=s",\r
+      "regression",\r
+    ) || usage "Invalid parameter";\r
+\r
+    # Restore @ARGV\r
+    @ARGV = @savedOptions;\r
+\r
+    # Restore the original command line options:\r
+    %_opts = %_cmdline_opts;\r
+\r
+    # Merge in %suiteOptions: Set into %_opts only if that key doesn't\r
+    # exist already. This allows command line options to override\r
+    # options specified on the .test line in the .suite file\r
+    foreach (keys %suiteOptions) {\r
+      $_opts{$_} = $suiteOptions{$_} unless $_opts{$_};\r
+    } # foreach\r
+\r
+    $suiteFailures += runTestFile $testFile;\r
+\r
+    # Need to disconnect $_east to shut down the previous run\r
+    my $savedSuiteID = $_east->{suiteID};\r
+\r
+    $_east->disconnect;\r
+\r
+    # Collect logfiles\r
+    $_east->collectLogFiles;\r
+\r
+    if ($suiteFailures == 0 and $_opts{val}) {\r
+      my @validations = ref $_opts{val} eq "ARRAY"\r
+                     ? @{$_opts{val}}\r
+                     : ($_opts{val});\r
+\r
+      $suiteFailures += runValidations @validations;\r
+    } # if\r
+\r
+    $_east = new Nethawk::East;\r
+\r
+    $_east->{suiteID} = $savedSuiteID;\r
+\r
+    $_connected = 0;\r
+  } # foreach\r
+\r
+  # Log suite results\r
+  verbose BOLD MAGENTA "Suite\t" . RESET GREEN $suiteName\r
+    . (($suiteFailures == 0) ? GREEN " Success" : RED " Failure") . RESET;\r
+\r
+  $_log->log ("Suite\t$suiteName" . ($suiteFailures == 0) ? "Success" : "Failure");\r
+\r
+  my $errMsg;\r
+\r
+  ($_east->{suiteID}, $errMsg) = $_rantestdb->endSuiterun (\r
+    name       => fileparse ($file, ".suite"),\r
+    start      => UnixDatetime2SQLDatetime (scalar (localtime ($suiteStartTime))),\r
+    result     => $suiteFailures ? "Failure" : "Success",\r
+  );\r
+\r
+  error $errMsg if $_east->{suiteID} != 0;\r
+\r
+  return $suiteFailures;\r
+} # runSuiteFile\r
+\r
+sub runFile ($) {\r
+  my ($file) = @_;\r
+\r
+  unless (-e $file) {\r
+    error "File $file does not exist";\r
+    return;\r
+  } # if\r
+\r
+  $_term->AddHistory ("source $file")\r
+    unless $_debugging or !-t STDIN;\r
+\r
+  $SIG{INT} = \&interrupted;\r
+\r
+  # Determine file type\r
+  if ($file =~ /\.test$/) {\r
+    runTestFile $file;\r
+  } elsif ($file =~ /\.suite$/) {\r
+    return runSuiteFile $file\r
+  } else {\r
+    error "File $file is not a .suite or .test file", 1;\r
+  } # if\r
+} # runFile\r
+\r
+$SIG{TERM}     =\r
+$SIG{QUIT}     = \&saveHistory;\r
+\r
+# Set a more friendly umask\r
+umask 002;\r
+\r
+GetOptions (\r
+  \%_opts,\r
+  verbose      => sub { set_verbose },\r
+  debug                => sub { set_debug },\r
+  usage                => sub { usage; exit 0 },\r
+  "eastview:s",\r
+  "view:s",\r
+  "tm500view:s",\r
+  "nmsview:s",\r
+  "type:s",\r
+  "class:s",\r
+  "unit:s",\r
+  "test:s",\r
+  "file:s",\r
+  "rfr:s",\r
+  "checkin_on_error",\r
+  "feature:s",\r
+  "secure!",\r
+  "regression",\r
+) || usage "Invalid parameter";\r
+\r
+# Special case elock command\r
+if (scalar @ARGV > 0 and $ARGV[0] =~ /elock/i) {\r
+  eLock ($ARGV[1]);\r
+  exit;\r
+} # if\r
+\r
+usage "Extraneous parameters: " . join " ", @ARGV if scalar @ARGV > 0;\r
+\r
+# Check for mutually exclusive options\r
+if ($_opts{file}) {\r
+  my $suffix = $ENV{RANTEST_FILE} ? "\nNote: The environment variable RANTEST_FILE is set" : "";\r
+\r
+  if ($_opts{test}) {\r
+    $suffix .= $ENV{RANTEST_TEST} ? "\nNote: The environment variable RANTEST_TEST is set" : "";\r
+  } elsif ($_opts{class}) {\r
+    $suffix .= $ENV{RANTEST_CLASS} ? "\nNote: The environment variable RANTEST_CLASS is set" : "";\r
+  } # if\r
+\r
+  usage "<test> and <file> are mutually exclusive$suffix"  if $_opts{test};\r
+  usage "<class> and <file> are mutually exclusive$suffix" if $_opts{class};\r
+} # if\r
+\r
+if ($_opts{eastview}) {\r
+  my $suffix = $ENV{RANTEST_VIEW} ? "\nNote: The environment variable RANTEST_VIEW is set" : "";\r
+  $suffix .= $ENV{RANTEST_EASTVIEW} ? "\nNote: The environment variable RANTEST_EASTVIEW is set" : "";\r
+\r
+  usage "<eastview> and <view> are mutually exclusive$suffix" if $_opts{view};\r
+} # if\r
+\r
+usage "-rfr and -regression are mutually exclusive" if $_opts{rfr} && $_opts{regression};\r
+\r
+# Make "view" an alias for "eastview" but only if there is no eastview\r
+# already defined\r
+$_opts{eastview} = delete $_opts{view} if $_opts{view} && !$_opts{eastview};\r
+\r
+# Check for required parameters\r
+usage "-view or -eastview specified but no view given"\r
+  if defined $_opts{view} and $_opts{view} eq "";\r
+usage "-tm500view specified but no view given"\r
+  if defined $_opts{tm500view} and $_opts{tm500view} eq "";\r
+usage "-nmsview specified but no view given"\r
+  if defined $_opts{nmsview} and $_opts{nmsview} eq "";\r
+usage "-type specified but no type given"\r
+  if defined $_opts{type} and $_opts{type} eq "";\r
+usage "-class specified but no class given"\r
+  if defined $_opts{class} and $_opts{class} eq "";\r
+usage "-unit specified but no unit # given"\r
+  if defined $_opts{unit} and $_opts{unit} eq "";\r
+usage "-test specified but no test given"\r
+  if defined $_opts{test} and $_opts{test} eq "";\r
+usage "-file specified but no file given"\r
+  if defined $_opts{file} and $_opts{file} eq "";\r
+usage "-rfr specified but no testcase ID given"\r
+  if defined $_opts{rfr} and $_opts{rfr} eq "";\r
+\r
+# Save these original command line options. If we are in suite mode\r
+# then we must allow the individual .test options override these\r
+# original command line options.  If -rfr is on then we are by default\r
+# verbose\r
+%_cmdline_opts = %_opts;\r
+\r
+set_verbose if $_opts{rfr};\r
+\r
+# Instantiate a new East object\r
+$_east = new Nethawk::East;\r
+\r
+# Set testcase ID into East object\r
+$_east->setTestCaseID ($_opts{rfr});\r
+\r
+$_debugging = get_debug;\r
+\r
+# If we are debugging (and thus STDIN gets confused between the debugger's\r
+# STDIN and rantest's STDIN) or if we don't have a tty (-t - we would not \r
+# have a tty if run from say cron(1m), then do not perform these actions\r
+# on $_term.\r
+unless ($_debugging or !-t STDIN) {\r
+  $_term = new Term::ReadLine $FindBin::Script;\r
+\r
+  $_term->{AUTORESET} = 1;\r
+\r
+  # Restore previous history, if any\r
+  $_term->ReadHistory (HISTORY_FILE);\r
+} # unless\r
+\r
+# Announce ourselves\r
+verbose DESC . RESET;\r
+\r
+# Open Rantest Database\r
+$_rantestdb = new RantestDB ("pswit", "!qaz2w3e");\r
+\r
+if ($_opts{test}) {\r
+  $SIG{INT} = \&interrupted;\r
+\r
+  $_stats{Run}++;\r
+\r
+  # Separate off options\r
+  my $testName = $_opts{test};\r
+\r
+  if ($_opts{test} =~ /(\S+)\s+\-.*$/) {\r
+    $testName = $1;\r
+ } # if\r
+\r
+  $testName = fileparse ($testName, "\.profile");\r
+\r
+  $_east->setSaveTo ("$testName/$_opts{type}$_opts{unit}/" . YMDHMS);\r
+\r
+  eval { mkpath LOGBASE . "/" . $_east->getSaveTo };\r
+\r
+  return (1, $@) if $@;\r
+\r
+  chmod 0775, LOGBASE . "/" . $_east->getSaveTo;\r
+\r
+  $_log = new Logger (\r
+    name       => $testName,\r
+    path       => LOGBASE . "/" . $_east->getSaveTo,\r
+    append     => "yes",\r
+  );\r
+\r
+  $_log->log ("$FindBin::Script Version " . VERSION_NBR . "\nUsing view: $_opts{eastview}");\r
+\r
+  executeTestStep;\r
+\r
+  $_term->AddHistory ("$_opts{class} $_opts{test}") unless $_debugging && -t STDIN;\r
+\r
+  # Disconnect from EAST\r
+  $_east->disconnect;\r
+\r
+  # Collect logfiles\r
+  $_east->collectLogFiles;\r
+} elsif ($_opts{file}) {\r
+  runFile $_opts{file};\r
+} else {\r
+  $_east->setSaveTo ("rantest/" . YMDHMS);\r
+\r
+  eval { mkpath LOGBASE . "/" . $_east->getSaveTo };\r
+\r
+  return (1, $@) if $@;\r
+\r
+  chmod 0777, LOGBASE . "/" . $_east->getSaveTo;\r
+\r
+  $_log = new Logger (\r
+    path       => LOGBASE . "/" . $_east->getSaveTo,\r
+    append     => "yes"\r
+  );\r
+\r
+  display DESC if !get_verbose;\r
+\r
+  if ($_opts{eastview}) {\r
+    $_log->log ("$FindBin::Script Version " . VERSION_NBR . "\nUsing view: $_opts{eastview}");\r
+  } else {\r
+    $_log->log ("$FindBin::Script Version " . VERSION_NBR);\r
+  } # if\r
+\r
+  set_verbose;\r
+\r
+  while () {\r
+    my $cmd;\r
+\r
+    unless ($_debugging) {\r
+      $cmd = $_term->readline (PROMPT . RESET);\r
+    } else {\r
+      display_nolf PROMPT . RESET;\r
+\r
+      $cmd = <STDIN>;\r
+    } # if\r
+\r
+    # Handle Control-d\r
+    unless (defined $cmd) {\r
+      display "";\r
+      saveHistory;\r
+      exit 0;\r
+    } # if\r
+\r
+    chomp $cmd;\r
+\r
+    next if $cmd eq "";\r
+\r
+    if ($cmd =~ /exit|quit/i) {\r
+      $_term->remove_history ($_term->where_history);\r
+      saveHistory;\r
+      exit 0;\r
+    } # if\r
+\r
+    if ($cmd =~ /^elock/i) {\r
+      if ($cmd =~ /^elock\s+(\w+)/i) {\r
+       eLock $1;\r
+      } else {\r
+       eLock;\r
+      } # if\r
+\r
+      next;\r
+    } # if\r
+\r
+    my @tokens = split /\s+/, $cmd;\r
+\r
+    $_opts{class}      = shift @tokens;\r
+    $_opts{test}       = join " ", @tokens;\r
+\r
+    $cmd = lc $_opts{class};\r
+\r
+    if ($cmd eq "help") {\r
+      help;\r
+      $_term->remove_history ($_term->where_history) unless $_debugging;\r
+      next;\r
+    } elsif ($cmd eq "usage") {\r
+      usage;\r
+      $_term->remove_history ($_term->where_history) unless $_debugging;\r
+      next;\r
+    } elsif ($cmd eq "version") {\r
+      display DESC;\r
+      $_term->remove_history ($_term->where_history) unless $_debugging;\r
+      next;\r
+    } elsif ($cmd eq "source") {\r
+      runFile $tokens[0];\r
+    } elsif ($cmd eq "set") {\r
+      if ($_opts{test} =~ /\s*(\w+)\s*=\s*(.+)/) {\r
+       my $optionName  = $1;\r
+       my $value       = $2;\r
+\r
+       # Remove quotes, if any. Note no check for balancing.\r
+       $value =~ s/[\"\']//g;\r
+\r
+       # Set option\r
+       $_opts{$optionName} = $value;\r
+      } # if\r
+    } elsif ($cmd eq "get") {\r
+      if ($_opts{$tokens[0]}) {\r
+       display "$tokens[0] = $_opts{$tokens[0]}";\r
+      } else {\r
+       display "$tokens[0] is not set";\r
+      } # if\r
+    } else {\r
+      $_stats{Run}++;\r
+      $_opts{class} = lc $_opts{class};\r
+\r
+      if ( $_opts{class} eq "manual" ) {\r
+        $_opts{test} = " ";\r
+      } # if\r
+\r
+      executeTestStep;\r
+    } # if\r
+  } # while\r
+\r
+  # Disconnect from EAST\r
+  $_east->disconnect;\r
+\r
+  # Assign 'Failed' and 'Timedout' 0 if they are not initialized\r
+  $_stats{Failed}   ||= 0;\r
+  $_stats{Timedout} ||= 0;\r
+\r
+  my $testErrors = $_stats{Failed} + $_stats{Timedout};\r
+\r
+  # Collect log files and check them in based on checkin_on_error option\r
+  $_east->collectLogFiles($testErrors, $_opts{checkin_on_error});\r
+\r
+} # if\r
+\r
+saveHistory;\r
+displaySummary;\r
+\r
+# The combination of Failed and Timedout represents our exit\r
+# status. If either or both of them is defined then they will be\r
+# non-zero and thus we exit with a non-zero status. Only if both are\r
+# undefined, and thus set to 0 by the code below, will we exit 0.\r
+$_stats{Failed}                = 0 unless $_stats{Failed};\r
+$_stats{Timedout}      = 0 unless $_stats{Timedout};\r
+\r
+# Now exit with the correct status\r
+exit ($_stats{Failed} + $_stats{Timedout});\r
diff --git a/rantest/web/Rantest/AverageRunTime.php b/rantest/web/Rantest/AverageRunTime.php
new file mode 100644 (file)
index 0000000..8612133
--- /dev/null
@@ -0,0 +1,223 @@
+<?php
+////////////////////////////////////////////////////////////////////////////////
+//
+// File:       AverageRunTime.php
+// Revision:   1.2
+// Description:        Produce a report of the average run time
+// Author:     Andrew@ClearSCM.com
+// Created:    Mon Apr 28 15:20:06 MST 2008
+// Modified:   
+// Language:   PHP
+//
+// (c) Copyright 2008, General Dynamics, all rights reserved.
+//
+// All rights reserved except as subject to DFARS 252.227-7014 of contract
+// number CP02H8901N issued under prime contract N00039-04-C-2009.
+//
+// Warning: This document contains technical data whose export is restricted
+// by the Arms Export Control Act (Title 22, U.S.C., Sec 2751, et seq.) or the
+// Export Administration Act of 1979, as amended, Title, 50, U.S.C., App. 2401
+// et seq. Violations of these export laws are subject to severe criminal
+// penalties. Disseminate in accordance with provisions of DoD Directive
+// 5230.25.
+//
+////////////////////////////////////////////////////////////////////////////////
+$script = basename ($_SERVER["PHP_SELF"]);
+include_once "$_SERVER[DOCUMENT_ROOT]/php/Utils.php";
+include_once "$_SERVER[DOCUMENT_ROOT]/php/RantestDB.php";
+
+$build = $_REQUEST["build"];
+$level = $_REQUEST["level"];
+$DUT   = $_REQUEST["DUT"];
+$test  = $_REQUEST["test"];
+
+if (!isset ($test) or ($test == "")) {
+  $test = "%";
+} // if
+
+// Replace "*"'s with "%"'s
+$build = preg_replace ("/\*/", "%", $build);
+$level = preg_replace ("/\*/", "%", $level);
+$DUT   = preg_replace ("/\*/", "%", $DUT);
+$test  = preg_replace ("/\*/", "%", $test);
+
+if ($build == "%" and
+    $level == "%" and
+    $DUT   == "%" and
+    $test  == "%") {
+  $testcase = "<All Tests>";
+} else {
+  $testcase  = "${build}_${level}_${DUT}_${test}";
+} // if
+
+$testname2 = preg_replace ("/%/", "*", $testcase);
+?>
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
+   "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+  <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+  <link rel="stylesheet" type="text/css" media="screen" href="/css/Testing.css">
+  <link rel="stylesheet" type="text/css" media="screen" href="/css/Tables.css">
+  <title>Test history for <?php print ($testcase == "<All Tests>") ? "All Tests" : $testname2;?></title>
+
+<body>
+
+<h1 align="center">Test history for <?php print ($testcase == "<All Tests>") ? "All Tests" : $testname2;?></h1>
+
+<?php
+  if ($testcase == "<All Tests>") {
+    print <<<END
+<table align=center width=40%>
+  <thead>
+    <tr>
+      <th class=left>#</th>
+      <th>Test case</th>
+      <th>Passed</th>
+      <th>Failed</th>
+      <th class=right>Total</th>
+END;
+  } else {
+    print <<<END
+<table align=center>
+  <thead>
+    <tr>
+      <th class=left>#</th>
+      <th>Test case</th>
+      <th>Start</th>
+      <th>End</th>
+      <th>Logs</th>
+      <th class=right>Result</th> 
+END;
+  } // if
+?>
+    </tr>
+  </thead>
+  <tbody>
+
+<?php
+OpenDB ();
+
+$row_nbr = 0;
+
+if ($testcase == "<All Tests>") {
+  $statement = <<<END
+select
+  test.name            as testname,
+  count(*)             as count,
+  status.name          as status
+from
+  testruns,
+  status,
+  test
+where
+  test.id = testruns.tcid      and 
+  testruns.statusid = status.id
+group by
+  testname,
+  status
+END;
+
+  $result = mysql_query ($statement)
+    or DBError ("Unable to execute query: ", $statement);
+
+  $lastTestcase = "unknown";
+
+  $passed  = 0;
+  $failed  = 0;
+
+  while ($row = mysql_fetch_array ($result)) {
+    $logs = logs ($row["eastlogs"]);
+
+    if ($row["testcase"] == $lastTestcase) {
+      if ($row["status"] == "Success") {
+         $passed = $row["count"];
+      } else {
+         $failed = $row["count"];
+      } // if
+
+      $row_color = ($row_nbr++ % 2 == 0) ? " class=other" : "";
+
+      $total = $passed + $failed;
+
+      print <<<END
+        <tr $row_color>
+          <td align=center>$row_nbr</td>
+          <td><a href="$script?testcase=$row[testname]">$row[testname]</a></td>
+          <td align=right>$passed</td>
+          <td align=right>$failed</td>
+          <td align=right>$total</td>
+      </tr>
+END;
+      $lastTestcase = "unknown";
+
+      $passed = 0;
+      $failed = 0;
+    } else {
+      $lastTestcase = $row["testcase"];
+
+      if ($row["status"] == "Success") {
+         $passed = $row["count"];
+      } else {
+         $failed = $row["count"];
+      } // if
+    } // if
+  } // while
+} else {
+  $statement = <<<END
+select
+  testruns.runid       as runid,
+  testrun.start                as start,
+  testruns.end         as end,
+  test.name            as testname,
+  status.name          as status,
+  testruns.eastlogs    as eastlogs
+from
+  testrun,
+  testruns,
+  status,
+  test
+where
+  test.name            like "$testcase"        and
+  test.id              = testruns.tcid         and
+  testrun.id           = testruns.runid        and
+  testruns.statusid    = status.id
+order by
+  left(start,10) desc,
+  testname
+END;
+
+  $result = mysql_query ($statement)
+    or DBError ("Unable to execute query: ", $statement);
+
+  while ($row = mysql_fetch_array ($result)) {
+    $class  = SetRowColor ($row["status"]);
+    $status = colorResult ($row["status"]);
+    $date   = YMD2MDY (substr ($row["start"], 0, 10));
+
+    $row_nbr++;
+    $logs = logs ($row["eastlogs"]);
+
+    print <<<END
+      <tr $class>
+        <td align=center>$row_nbr</td>
+        <td><a href="/rantest.php?testName=$row[testname]&runID=$row[runid]&date=$date">$row[testname]</a></td>
+        <td align=center>$row[start]</td>
+        <td align=center>$row[end]</td>
+        <td>$logs</td>
+        <td>$status</td>
+      </tr>
+END;
+   } // while
+}// if
+
+print <<<END
+  </tbody>
+</table>
+END;
+
+copyright ();
+?>
+</body>
+</html>
+
diff --git a/rantest/web/Rantest/ChangeLog.php b/rantest/web/Rantest/ChangeLog.php
new file mode 100644 (file)
index 0000000..9307dbb
--- /dev/null
@@ -0,0 +1,212 @@
+<?php
+////////////////////////////////////////////////////////////////////////////////
+//
+// File:       ChangeLog.php
+// Revision:   1.2
+// Description:        Change log for 1.2
+// Author:     Andrew@ClearSCM.com
+// Created:    Mon Apr 28 15:20:06 MST 2008
+// Modified:   
+// Language:   PHP
+//
+// (c) Copyright 2008, General Dynamics, all rights reserved.
+//
+// All rights reserved except as subject to DFARS 252.227-7014 of contract
+// number CP02H8901N issued under prime contract N00039-04-C-2009.
+//
+// Warning: This document contains technical data whose export is restricted
+// by the Arms Export Control Act (Title 22, U.S.C., Sec 2751, et seq.) or the
+// Export Administration Act of 1979, as amended, Title, 50, U.S.C., App. 2401
+// et seq. Violations of these export laws are subject to severe criminal
+// penalties. Disseminate in accordance with provisions of DoD Directive
+// 5230.25.
+//
+////////////////////////////////////////////////////////////////////////////////
+$script = basename ($_SERVER["PHP_SELF"]);
+
+include_once "$_SERVER[DOCUMENT_ROOT]/php/Utils.php";
+include_once "$_SERVER[DOCUMENT_ROOT]/php/RantestDB.php";
+?>
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
+   "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+  <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+  <link rel="stylesheet" type="text/css" media="screen" href="/css/Testing.css">
+  <link rel="stylesheet" type="text/css" media="screen" href="/css/Tables.nohover.css">
+  <title>RANTEST: ChangeLog</title>
+</style>
+</head>
+
+<body>
+
+<?php print banner ();?>
+
+<h1 align=center>RANTEST 1.2 ChangeLog</h1>
+
+<ul>
+   <li><a href="#1.2.3">Version 1.2.3</a></li>
+
+   <li><a href="#1.2.2">Version 1.2.2</a></li>
+
+   <li><a href="#1.2.1">Version 1.2.1</a></li>
+
+   <li><a href="#1.2">Version 1.2</a></li>
+</ul>
+
+<p>This is the ChangeLog for RANTEST for versions 1.2 and up.</p>
+
+<p>See also:</p>
+
+<ul>
+  <li><a href="ChangeLog0.9.php">RANTEST 0.9 ChangeLog</a></li>
+
+  <li><a href="ChangeLog1.0.php">RANTEST 1.0 ChangeLog</a></li>
+
+  <li><a href="ChangeLog1.1.php">RANTEST 1.1 ChangeLog</a></li>
+</ul>
+
+<h2><a name="1.2.4">Version 1.2.4</a></h2><hr>
+
+<h3>Rantest:</h3>
+
+<ul>
+  <li>Fixed bug with -nosecure</li>
+</ul>
+
+<h2><a name="1.2.3">Version 1.2.3</a></h2><hr>
+
+<h3>East:</h3>
+
+<ul>
+  <li>Added running of securenode</li>
+</ul>
+
+<h3>Rantest:</h3>
+
+<ul>
+  <li>New -secure parameter, tells East.pm to secure the
+  node. Default: -secure - use -nosecure to turn off.</li>
+<ul>
+
+<h2><a name="1.2.3">Version 1.2.3</a></h2><hr>
+
+<h3>East.pm:</h3>
+
+<ul>
+  <li>Added 'manual' option when running tests which will
+  open a shell for users to run any number of
+  manual tests.  Users will then be prompted as to whether
+  or not the tests passed after exiting the shell.</li>
+
+</ul>
+
+<h3>RantestDB.php:</h3>
+
+<ul>
+  <li>resolved sort issue with date column</li>
+
+</ul>
+
+<h2><a name="1.2.2">Version 1.2.2</a></h2><hr>
+
+<h3>East.pm:</h3>
+
+<ul>
+  <li>Included prompt for users to skip check in of log files if
+  there were errors in the test(s).</li>
+
+</ul>
+
+<h2><a name="1.2.1">Version 1.2.1</a></h2><hr>
+
+<h3>Web:</h3>
+
+<ul>
+  <li>Fixed a bug with TestHistory.php where it fails to sort by
+  column. The real problem was how it was passing the testcase
+  parameter in the URL. It was using the MySQL wildcard of "%", which
+  is an escape character in URLs. The fix involves only ever using and
+  showing the user "*" instead of "%". Then, internally, converting
+  "*"'s -> "%"'s.</li>
+
+  <li>Fixed change logs to have the banner.</li>
+
+  <li>Fixed GraphStats.php to no longer use the hard coding to my view.</li>
+
+  <li>Added protection to exported CSV filenames so that they don't
+  contain "*"'s.</li>
+
+</ul>
+
+<h2><a name="1.2">Version 1.2</a></h2><hr>
+
+<h3>Web:</h3>
+
+<ul>
+  <li>General redesign of web pages. New color scheme, etc. In general for all pages:</li>
+
+  <ul>
+    <li>Added General Dynamics banner to all pages. Note that General
+    Dynamics in the banner is also a link to "home" (clicking on it
+    gets to to the main page).</li>
+
+    <li>Added <input type="submit" value="Export to CSV">
+    functionality to all pages. Now you can export the current web
+    page to a CSV file for use with Excel/Open Office.</li>
+
+    <li>Added <b>Email to</b> functionality to pages. To use select a
+    user in the dropdown (dynamically created list of users from NIS)
+    and click <input type="submit" value="Send">. An email will be
+    sent to that user with the contents of the web page rendered into
+    HTML in their Outlook inbox! Also a .csv file of the page will be
+    attached!</li>
+
+    <li>Added sorting by column headers. Click on a column to
+    sort. Click on the same column to sort in reverse order.</li>
+  </ul>
+
+  <li>New report: <i>Failure Analysis</i>. This report shows you the
+  test steps that failured for a particular day.</li>
+
+  <li>Moved date dropdowns into the report headings. For example the
+  <i>Daily Test Report</i> and <i>Failure Analysis</i> reports now have the
+  date dropdown in the heading itself.</li>
+
+  <li>Added <input type="submit" value="Analyze Failures"> button to
+  <i>Daily Test Report</i></li>
+
+  <li>Added graph to <i>Test Statistics</i> report! This graph shows
+  the daily testing over time with pass/fail. You can also select a
+  date range and test type and regenerate the graph. Also added <input
+  type="submit" value="Report"> button on graph to switch to the
+  report and a <input type="submit" value="Graph"> button on report to
+  switch to the graph.</li>
+
+  <li>Filled out <i>Testcase per Version</i> and <i>Version per
+  Testcase</i> reports.</li>
+
+  <li>Added <input type="submit" value="History"> button to <i>Test
+  Steps</i> report. This allows you to easily see the history for a
+  test.</li>
+
+  <li>Changed <b>Run by</b> to use the actual person's name and to
+  make it a mailto link in the <i>Test Steps</i> report.</li>
+
+  <li>Added the ability to filter based on <b>Type</b> (All, Normal,
+  Regression) on <i>Daily Test Report</i>.</li>
+
+  <li>Moved <b>Nightly Logs</b> to bottom of the main page and
+  arranged the links to at maximum 5 per row.</li>
+</ul>
+
+<h3>Rantest DB</h3>
+
+<ul>
+  <li>Added indexes to database to speed up retrieval of certain
+  queries</li>
+</ul>
+
+<?php copyright();?>
+</body>
+</html>
diff --git a/rantest/web/Rantest/ChangeLog0.9.php b/rantest/web/Rantest/ChangeLog0.9.php
new file mode 100644 (file)
index 0000000..1189d90
--- /dev/null
@@ -0,0 +1,739 @@
+<?php
+////////////////////////////////////////////////////////////////////////////////
+//
+// File:       Changelog0.9.php
+// Revision:   1.2
+// Description:        Change log for Rantest 0.9
+// Author:     Andrew@ClearSCM.com
+// Created:    Mon Apr 28 15:20:06 MST 2008
+// Modified:   
+// Language:   PHP
+//
+// (c) Copyright 2008, General Dynamics, all rights reserved.
+//
+// All rights reserved except as subject to DFARS 252.227-7014 of contract
+// number CP02H8901N issued under prime contract N00039-04-C-2009.
+//
+// Warning: This document contains technical data whose export is restricted
+// by the Arms Export Control Act (Title 22, U.S.C., Sec 2751, et seq.) or the
+// Export Administration Act of 1979, as amended, Title, 50, U.S.C., App. 2401
+// et seq. Violations of these export laws are subject to severe criminal
+// penalties. Disseminate in accordance with provisions of DoD Directive
+// 5230.25.
+//
+////////////////////////////////////////////////////////////////////////////////
+$script = basename ($_SERVER["PHP_SELF"]);
+
+include_once "$_SERVER[DOCUMENT_ROOT]/php/Utils.php";
+include_once "$_SERVER[DOCUMENT_ROOT]/php/RantestDB.php";
+?>
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
+   "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+  <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+  <link rel="stylesheet" type="text/css" media="screen" href="/css/Testing.css">
+  <link rel="stylesheet" type="text/css" media="screen" href="/css/Tables.nohover.css">
+  <title>RANTEST: ChangeLog 0.9</title>
+</style>
+</head>
+
+<body>
+
+<?php print banner ();?>
+
+<h1 align=center>RANTEST 0.9 ChangeLog</h1>
+
+<ul>
+   <li><a href="#0.9.9c">Version 0.9.9c</a></li>
+
+   <li><a href="#0.9.9b">Version 0.9.9b</a></li>
+
+   <li><a href="#0.9.9a">Version 0.9.9a</a></li>
+
+   <li><a href="#0.9.9">Version 0.9.9 - Code Complete</a></li>
+
+   <li><a href="#0.9.8d">Version 0.9.8d</a></li>
+
+   <li><a href="#0.9.8c">Version 0.9.8c</a></li>
+
+   <li><a href="#0.9.8b">Version 0.9.8b</a></li>
+
+   <li><a href="#0.9.8a">Version 0.9.8a</a></li>
+
+   <li><a href="#0.9.8">Version 0.9.8</a></li>
+
+   <li><a href="#0.9.7a">Version 0.9.7a</a></li>
+
+   <li><a href="#0.9.7">Version 0.9.7 - "The Rantvl release"</a></li>
+
+   <li><a href="#0.9.6">Version 0.9.6</a></li>
+
+   <li><a href="#0.9.5">Version 0.9.5</a></li>
+</ul>
+
+<p>This is the ChangeLog for RANTEST for versions 0.9 and up.</p>
+
+<p>See also:</p>
+
+<ul>
+  <li><a href="ChangeLog1.0.php">RANTEST 1.0 ChangeLog</a></li>
+
+  <li><a href="ChangeLog1.1.php">RANTEST 1.1 ChangeLog</a></li>
+
+  <li><a href="ChangeLog.php">RANTEST 1.2 ChangeLog</a></li>
+</ul>
+
+<h2><a name="0.9.9c">Version 0.9.9c</a></h2><hr>
+
+<h3>East.pm:</h3>
+
+<ul>
+  <li>Changed MAX_ATTEMPTS to 4</li>
+
+  <li>Moved capturing of $startTime into the do/until statement</li>
+
+  <li>Added some LogDebug statements to trace what's happening with
+  this auto-rerun stuff.</li>
+
+  <li>Changed if statement such that the warning would come out on the
+  first re-run. I suspect impatient engineers were not waiting for the
+  3 iteration when the first warning would come out.</li>
+
+  <li>Changed to use /vobs/nms_log for NMS logs. Vob's been replicated
+  but not put into project yet. This should happen soon however.</li>
+</ul>
+
+<h3>domsh</h3>
+
+<ul>
+  <li>Changed to handle the situation where -pattern is not
+  specified.</li>
+
+  <li>Added [OUTPUT][/OUTPUT] and [STATUS][/STATUS] tags.</li>
+</ul>
+
+<h3>ranscrub</h3>
+
+<ul>
+  <li>Fixed bug with finding ranscrub's conf file.</li>
+</ul>
+
+<h3>Web:</h3>
+
+<ul>
+  <li>Added <i>Testcase Per Version</i> report.</li>
+
+  <li>Added support for multiple nightly logs</li>
+
+  <li>Added link to <i>Testcase Per Version</i> report.</li>
+</ul>
+
+<h2><a name="0.9.9b">Version 0.9.9b</a></h2><hr>
+
+<h3>rantest:</h3>
+
+<ul>
+  <li>Added runValidation subroutine that handles running of a
+  validation and also adds to the rantest database the validation test
+  step.</li>
+
+  <li>Fixed up some errors in the execution of valMsgs.pl
+  validation. This included missing the testlogs component of the
+  path, adding the leading "/" before Rantvl, using $_opts{type}
+  instead of the missing $_opts{unitType} and adding on the ">" for
+  proper redirection.</li>
+</ul>
+
+<h3>ranscrub:</h3>
+
+<ul>
+  <li>Added logging to ranscrub. It will now log to testlogs.</li>
+</ul>
+
+<h3>donightly:</h3>
+
+<ul>
+  <li>Will now accept a parameter for the DUT in question. This allows
+  a poor man's way of doing parallelization by creating .suite or
+  .test files that have the DUT as part of the filename and adding
+  multiple lines in cron(1).</li>
+
+  <li>Because of the above: Removed the updating or
+  rantest.stats.csv.</li>
+</ul>
+
+<h3>Web:</h3>
+
+<ul>
+  <li>Added new report: Test Case Per version.</li>
+
+  <li>Removed useless reports on many page</li>
+
+  <li>Added average run time to Test History Report but only for when
+  all tests are selected.</li>
+</ul>
+
+<h2><a name="0.9.9a">Version 0.9.9a</a></h2><hr>
+
+<h3>East.pm:</h3>
+
+<ul>
+  <li>Changed to not spawn extra processes when -timeout -1 was
+  used.</li>
+
+  <li>At Roman and Doug's request, added code to print out all of the
+  logfiles produced under logpath.</li>
+</ul>
+
+<h3>Web:</h3>
+
+<ul>
+  <li>RantestDB.php: Added new Error function</li>
+
+  <li>RantestDB.php: Added getStatus function like
+  RantestDB::getStatus function. This routine is the driver for the
+  new TestStats.php page.</li>
+
+  <li>TestHistory.php: Added total row.</li>
+
+  <li>index.php: Activated TestStats report</li>
+
+  <li>TestStats.php: Added TestStats report</li>
+</ul>
+
+<h3>Ranscrub:</h3>
+
+<ul>
+  <li>Added formatSize to format the size into a more human readable
+  form.</li>
+
+  <li>Added scrubbing of Rantvl</li>
+
+  <li>Added option handling for testlogs, rantvl and rantestdb
+  scrubbing as well as all - default all.</li>
+
+  <li>Added rantvl_user to config file and updated scrub_days and
+  db_scrub to 45 days.</li>
+</ul>
+
+<h3>Misc</h3>
+
+<ul>
+  <li>Changed update_view to use -x to avoid xauth problems</li>
+</ul>
+
+<h2><a name="0.9.9">Version 0.9.9 - Code Complete</a></h2><hr>
+
+<p>Reached code complete!</p>
+
+<h3>East.pm:</h3>
+
+<ul>
+  <li>Changed code to automatically create test case ID directories if
+  they are not present for <i>Run for Record</i></li>
+
+  <li>Changed to handle error output a little better.</li>
+</ul>
+
+<h2><a name="0.9.8d">Version 0.9.8d</a></h2><hr>
+
+<h3>rantest:</h3>
+
+<ul>
+  <li>Added code to capture output from validation scripts into a log
+  file</li>
+</ul>
+
+<h3>donightly</h3>
+
+<ul>
+  <li>Added code to put out the date, failures and successes into
+  .../auto/test/nightly/rantest.stats.csv for graphing purposes.</li>
+</ul>
+
+<h3>East.pm</h3>
+
+<ul>
+  <li>Changed handling of log collection again to handle run for
+  record capturing better.</li>
+
+  <li>Checking for /^\[LOG\]/ instead of /\[LOG\]/. Sometimes [LOG]
+  was appearing in ASCII dumps!</li>
+</ul>
+
+<h2><a name="0.9.8c">Version 0.9.8c</a></h2><hr>
+
+<h3>rantest:</h3>
+
+<ul>
+  <li>Emergency fix! Changing the flow of when log files are collected
+  after disconnection is fine. However when in suite mode we
+  disconnect for each .test file we run. Since disconnect no logner
+  automatically calls collectLogFiles we need to call it right after
+  each time we disconnect. We also need to run validations at that
+  time.</li>
+
+  <li>Changed collectLogFiles to not exit rantest merely because we
+  couldn't find a logfile. In suite mode there may be more to do.</li>
+</ul>
+
+<h2><a name="0.9.8b">Version 0.9.8b</a></h2><hr>
+
+<h3>rantest:</h3>
+
+<ul>
+  <li>I believe the persistent bug regarding running in cron is
+  finally fixed!</li>
+
+  <li>Removed the wait for processes to finish. This was done only
+  because GenericDesktopMonitor would be running for up to 10 seconds
+  after we stopped. Before, when we used to tar up things, this was
+  important. Sometimes the tar would report that the files changed
+  while tarring them. Since we don't make tars anymore this should not
+  be a problem. Might be a problem for Run for Record though...</li>
+
+  <li>Added verbose output to tell the user where the log files are
+  stored under testlogs. This will be helpful for manual validations
+  that are still required.</li>
+
+  <li>Fixed bug with grepping for "Simulator version is". Needed to
+  put quotes around that!</li>
+
+  <li>Changed name of rantest build logs vob to
+  rantest_build2_log. There is no rantest_log_build2 vob!</li>
+
+  <li>Changed running of validations to occur after collection of log
+  files. Otherwise it doesn't make sense to validate against logfiles
+  that haven't be collected.</li>
+</ul>
+
+<h3>Validation</h3>
+
+<ul>
+  <li>Renamed decode.test.FQT to aal2val</li>
+
+  <li>Created msgdefs directory</li>
+</ul>
+
+<h3>ranscrub</h3>
+
+<ul>
+  <li>Checked in bare bones ranscrub and config/ranscrub.conf</li>
+</ul>
+
+<h2><a name="0.9.8a">Version 0.9.8a</a></h2><hr>
+
+<h3>Web:</h3>
+
+<ul>
+  <li>Fixed borken link in copyright version</li>
+</ul>
+
+<h3>Rantest:</h3>
+
+<ul>
+  <li>Fixed bug with handling of ReadLine properly in interactive and
+  cron cases.</li>
+
+  <li>Fixed bug with errorneously calling validation routines when
+  there were none specified in test</li>
+
+  <li>Added progress indication in log collection</li>
+
+  <li>Fixed bug with keeping track of stats - errorneously adding file
+  failure count to test failure count</li>
+</ul>
+
+<h3>East.pm:</h3>
+
+<ul>
+  <li>Changed bugcatcher to only log to /tmp/rantest.debug.log</li>
+</ul>
+
+<h2><a name="0.9.8">Version 0.9.8</a></h2><hr>
+
+<h3>Web:</h3>
+
+<ul>
+  <li>Changed copyright block to highlight and link only the current
+  version</li>
+
+  <li>Added Last Modified into copyright block</li>
+</ul>
+
+<h3>Rantest:</h3>
+
+<ul>
+  <li>Added comment about suite files</li>
+
+  <li>Now properly returning $suiteFailures from runSuiteFile</li>
+
+  <li>Added runValidation subrountine. Now does rudimentary
+  validations for valMsgs.pl and aalval</li>
+
+  <li>Changed to use -t STDIN to distingish if we are running with a
+  tty. If not we are in cron and we have to behave differently for the
+  ReadLine stuff.</li>
+
+  <li>Now properly returning errors from runFile subroutine</li>
+
+  <li>Now ouputs a message telling the user if it is busy terminating
+  processes and collecting logfiles at the end of execution</li>
+</ul>
+
+<h3>East.pm:</h3>
+
+<ul>
+  <li>Changed bug catcher (for ls -t problem) to log all java
+  processes running to /tmp</lI>
+
+  <li>Added getCollectLogFiles method.</li>
+
+  <li>Refixed bugs in calls to error with join in subroutines like
+  _checkoutElement</li>
+
+  <li>Totally changed the way collectLogFiles work. For one, it no
+  longer creates tar files, except for run for record. The new
+  ranscrub (in development) will be responsible for cleaning up space
+  in testlogs and other areas. Doing this also removes the disposition
+  of logfiles from rantest's concern</li>
+
+  <li>Plus run for record runs now first store in testlogs then only
+  made as a tar directly into the appropriate Clearcase vob.</li>
+
+  <li>Based on the log type (EAST|TM500|NMS|RANTVL) the run for record
+  tar image is put into simdev_log, tm500_log, rantest_log_build2. For
+  that last one both NMS and RANTVL is stored into
+  rantest_log_build2. Yes I don't like the 2 in build 2! :-(</li>
+</ul>
+
+<h2><a name="0.9.7a">Version 0.9.7a</a></h2><hr>
+
+<p>Roll up fixes for <a href="#0.9.7">0.9.7</a>:</p>
+
+<ul>
+  <li>Changed handling of errors in collectLogFiles.  Since this is
+  happening from when disconnecting from EAST there's no sense in
+  attempting to return status or error messages, just die!  Also
+  changed the internal methods that collectLogFiles calls
+  (e.g. _makeTar, _checkOutElement, etc.) simply die too.</li>
+
+  <li>Now setting umask correctly to 002 (was setting it to 022!,
+  which did nothing different). With new umask setting we no longer
+  need to attempt to chmod directories to 775.</li>
+
+  <li>Removed DESTROY subroutine. Seems if we attempt to call
+  disconnect from DESTROY then it doesn't work well. Not really sure
+  why this happens but rantest now calls $east->disconnect
+  directly. We should investigate this further because it would be
+  much better if DESTROY did the right thing</li>
+
+  <li>Implemented new testtimeout option. This option, entered into
+  the .test file, will set an overall limit of how long rantest will
+  wait for all test steps to complete. This is done via an alarm
+  signal. So rantest sets the alarm at the start of execution of the
+  test steps and a signal handler to catch the alarm signal. If caught
+  then rantest disconnects from EAST. Not sure how smoothly things
+  proceed from there (does rantest continue to the next .test
+  file?)</li>
+
+  <li>Created runSuiteFile as a separate subroutine. This subroutine
+  will now parse off options after the .test file that override what's
+  in the .test file. This will allow us to specify "mytest.test -unit
+  6" so that even if mytest.test said to use unit 3, during this suite
+  run it would use unit 6.</li>
+
+  <li>Added user ID of who ran the test to the Test Step web page.</li>
+
+  <li>Added "news" link to this page</li>
+
+  <li>Added link to main page to the Nightly Log</li>
+
+  <li>Added/fixed p6000c and p4781c's ssh keys to authorized_keys</li>
+
+  <li>Added this ChangeLog</li>
+</ul>
+
+<h2><a name="0.9.7">Version 0.9.7 - "The Rantvl release"</a></h2><hr>
+
+<h3>Rantvl</h3>
+
+<p>A word about rantvl. This is a rantest_tools tool so it resides on
+the RAN, specifically under /prj/muosran/SWIT/tools/bin/rantvl. This
+Perl script contacts the DUT and sets up logging via a moshell script
+(which is why it needs to run on a RAN machine since moshell doesn't
+work from the Linux EAST environment). It then kicks off a number of
+processes that run in the background. All of these processes are
+spawned off as children of rantvl. Rantvl then wait(3)'s for either
+the children to finish (bad, bad - this is an error since the logging
+should never finish) or for it to be terminated.</p>
+
+<p>Rantest will open up a channel to RANHOST as RANUSER and run rantvl
+supplying it the -[rnc|rbs] [n] parameters as well as a -logpath
+parameter. The -logpath parameter is a path relative to
+ranray:/export/rantvl, since this filesystem seems to have space. We
+probably should verify and OK that. So then rantest will supply the
+logpath of &lt;testname&gt;/&lt;DUT&gt;/&lt;timestamp&gt;. (Note to
+Ken, while the logfiles may grow large, rantest/rantvl is pretty
+efficient in that once the test is completed these files are copied to
+our testlogs areas on seast1 and removed from
+ranray:/export/rantvl).</p>
+
+<p>Rantvl then will use that logpath to write it's logfiles. When
+rantest is done running tests in a .test file it collects the rantvl
+logfiles by scp'ing them from
+&lt;RANHOST&gt;:/export/rantvl/&lt;logpath&gt;/* to seast1's LOGBASE -
+/east/seast1/testlogs/&lt;logpath&gt;/Rantvl/*.  Rantest will then
+remove the logfiles from the source area
+(&lt;RANHOST&gt;:/export/rantvl/&lt;logpath&gt;/*) to conserve space.
+Rantvl is then added to the list of directories to tar up into a
+Rantvl.tgz (i.e.  <a
+href="http://rantestweb/testlogs/b2_l3_rnc_irt_001.rantvl/rnc5/20080527@14:20:59/">http://rantestweb/testlogs/b2_l3_rnc_irt_001.rantvl/rnc5/20080527@14:20:59/</a>).</p>
+
+<h3>General:</h3>
+
+<ul>
+  <li>Replaced bash script, update_view, with Perl script. Now does
+  -overwrite by default. Cronjob now calls it with -nooverwrite.</li>
+
+  <li>Added copy of authorized_keys to config. This is the ssh
+  authorized_keys file for all of the rantm50x machines as well as the
+  NMS simulator machines. This allows testers to ssh as pswit onto
+  these machines without needing a password. This facilitates
+  automation.</li>
+</ul>
+
+<h3>East.pm:</h3>
+
+<ul>
+  <li>Added support for rantvl.</li>
+
+  <li>Added LOGBASE</li>
+
+  <li>Added constants for RANHOST (ranray) and RANUSER (pswit). Both
+  Moshell and Rantvl need to connect remotely to a RAN machine as a
+  generic user. These constants facilitate that.</li>
+
+  <li>Changed to only attempt collection of log files if there log
+  files to collected. Rantest will set the member collectLogFiles to
+  true when there are log files to collect (i.e. when a test step is
+  attempted). It will also turn this off if the test run is
+  interrupted (Control-C) and abort is selected.</li>
+
+  <li>Added new set/getCollectLogFiles methods for the above.</li>
+
+  <li>Changed the logic in collectLogFiles a little bit to accommodate
+  collection of Rantvl log files too. When collecting Rantvl log files
+  they are scp'ed from RANHOST to the local host (in this case seast1)
+  under LOGBASE and then removed from RANHOST to conserve space.</li>
+
+  <li>Added pod documentation for new East methods.</li>
+</ul>
+
+<h3>Web:</h3>
+
+<ul>
+  <li>Put web pages formally under Clearcase control.
+  cclinux:/var/www/html now points directly to
+  ./pswit_auto/vobs/rantest_auto/web.</li>
+
+  <li>php/Utils.php now puts out the version of the web pages (0.9.7)
+  so that they stay in line with the version of rantest and
+  friends.</li>
+
+  <li>Added logic for new "test selectors" on the main page to
+  TestHistory.php.</li>
+
+  <li>Renamed links in the copyright section to shorter names. Also
+  added in Gantry's nice User's Guide: Home | Wiki | Users Guide |
+  Usage.</li>
+
+  <li>Changed RantestDB.php to categorize "Logging started" results
+  (from a rantvl log) in a similar fashion to "In progress" results as
+  the running of rantvl is really a backgrounded operation...</li>
+</ul>
+
+<h3>Rantest:</h3>
+
+<ul>
+  <li>Changed $east object to a global since the INT signal handler
+  now needs to set state in it and there's no other way to get to the
+  object except to make it global. This required removing $east from
+  all subroutines it used to be passed to.</li>
+
+  <li>Added logic to bail out on a .test run if one could not connect
+  to EAST. Rantest will now go to the next test in the case of suite
+  execution (since the next case may have a different rnc, etc.)</li>
+
+  <li>Added code to handle new rantvl type steps.</li>
+
+  <li>Added code to treat "Logging started" like "In progress".</li>
+
+  <li>Error out if we cannot create our "saveTo" directory.</li>
+
+  <li>Changed to set umask to 022 for reduce the problems created by
+  not creating objects as group writable.</li>
+
+  <li>Changed to classify both Failed's and Timeout's as error and
+  exit with a total of both of them. IOW only if there are no Failed's
+  and no Timeout's will an exit 0 be done.</li>
+
+  <li>Updated rantest wiki page to 0.9.7. Also includes link to
+  rantestweb.</li>
+</ul>
+
+<h2><a name="0.9.6">Version 0.9.6</a></h2><hr>
+
+<p>I'm delivering rantest 0.9.6 with the following set of features in
+anticipation of working on integrating rantvl for 0.9.7.</p>
+
+<p>New in 0.9.6:</p>
+
+<ul>
+  <li>Now properly records the full path to the log files for this run
+  in the databse as testruns.eastlogs.</li>
+
+  <li>Errors in creation of paths are reported better.</li>
+
+  <li>New directories that are created are chmod 0775 to facilitate
+  sharing. Note that not every component is chmoded - only the leaf
+  node.</li>
+
+  <li>Disposition of log directories that are tarred is set to "keep"
+  now. This means that there will be a tar file and the source
+  directory for easy navigation through the web. It also means that
+  our testlogs directory will get fuller, faster.</li>
+
+  <li>bin/donightly has come back with a similar purpose but different
+  implementation. It now runs all of the .test and .suite files in
+  ../auto/test/nightly.</li>
+
+  <li>Changed testruns.eastlogs and testruns.cmlogs from tinytext to
+  just text. These log file paths are pretty long and what's a few
+  bytes between friends?</li>
+
+  <li>Changed rantest to save original command line options and
+  restore them at the start of each .test run.</li>
+
+  <li>Changed rantest to save the testStartTime so that the start
+  times reported on the web match those in the path to the
+  logfiles. This allows the web loglinks to work.</li>
+
+  <li>Reorganized a lot of the web stuff and put it firmly under
+  Clearcase control.</li>
+
+  <li>Fixed problem with drop downs in Firefox (1.0) when doing hover
+  rollovers on main page.</li>
+
+  <li>Rearranged front page and added "test selectors" to a few of the
+  reports. Also made the Test History report properly respond to these
+  new test selectors. if all boxes are "*" then Test History does an
+  "All Tests" report, otherwise it filters the qualifying entries and
+  reports on them.</li>
+
+  <li>Added Loglinks to web site. Engineers can now drill down to the
+  actual tests and then into the logs themselves to diagnois
+  problems.</li>
+
+  <li>Removed Run By from Daily Test Report.</li>
+
+  <li>Changed Daily Test Report to report Unit and Version.</li>
+
+  <li>Added version number to copyright block on the web page.</li>
+
+  <li>Added getBuildNbr function to RantestDB.php.</li>
+
+  <li>Changed DBError calls to use __FUNCTION__.</li>
+</ul>
+
+<h2><a name="0.9.5">Version 0.9.5</a></h2><hr>
+
+<ul>
+  <li>Takes two extensions for -file, .suite or .test. The .test is
+  like the old .suite files (in fact, I've renamed the old .suite
+  files -&gt; test). Therefore the new .suite file is different - it's
+  simply a file listing a bunch of .test's to test.</li>
+
+  <li>Now uses LOGBASE, set to $ENV{MNT_DIR}/testlogs. All logs are
+  now stored under that.</li>
+
+  <li>Now uses TESTBASE, set to "/local/server/auto/test". This is the
+  base relative directory that rantest uses to look for tests and
+  suites.</li>
+
+  <li>Verbose output improved. Since rantest can run suites, which
+  contain other tests, we needed to properly represent this in the
+  verbose output.</li>
+
+  <li>The -saveto has been removed. Instead logs are saved to LOGBASE
+  (with appropriate subdirectory structure).</li>
+
+  <li>Added pattern to elock subcommand. Allows you to filter out
+  stuff. So "rantest elock rnc" will show only rnc's. Also, elock is a
+  short circut commands such that if it is given on the command line
+  as the above hints at, then elock is done and rantest exits. Note
+  elock still works inside rantest in interactive mode. Finally
+  colorized output.</li>
+
+  <li>recordRun changed to announceTestRun and is used before each
+  test run of a suite or individual test.</li>
+
+  <li>Added code to support new rantest DB format. Now we call
+  start[type]run and end[type]run (where type is Suite, Test, Step) to
+  record to the database the starting and ending of suites, tests and
+  test steps.</li>
+
+  <li>Changed runFile to call runTestFile for tests. runFile now
+  handles suite runs.</li>
+
+  <li>Changed East.pm to disconnect from East (i.e. shutdown
+  GenericDesktop and friends) after each execution of a test. This
+  requires that we start another one up for the next test in a
+  suite.</li>
+
+  <li>Added methods to set TestCaseID and saveTo directory so that
+  saveLogFiles can be called in the East object destructor.</li>
+
+  <li>saveLogFiles now properly copies log files from their source
+  locations to our new LOGBASE location before tarring them up.</li>
+
+  <li>saveLogFiles now copies subdirs of EASTLogs as per Ross'
+  request.</li>
+
+  <li>saveLogFiles now removes the copied subdirs after the tar to
+  conserve space.</li>
+</ul>
+
+<p>One outstanding problem remains that occasionally rantest is unable
+to find the logfile. I have a bugcatcher in 0.9.5 however it see ms
+like there is no logfile directory to find log files in. Let me
+restate, when we are done with testing we attempt to locate the
+logfile down
+$MNT_DIR/$EAST_REL/DUT/&lt;dut&gt;/data/logs/regression/testcase/&lt;testcase&gt;.
+At this point there should be a time stamped directory. So I'm doing
+an ls -t and picking off the first entry.</p>
+
+<p>What I've been finding is that all directories done the path to
+&lt;testcase&gt; exist but the &lt;testcase&gt; directory is no there!
+I can think of 3 reasons why this might be the case:</p>
+
+<ol>
+  <li>The directory just doesn't exist and rantest is right to
+  complain.</li>
+
+  <li>The directory does not exist yet because some background process
+  has not yet created it. Well when I was debugging this I was sitting
+  in the debugger looking around for quite some time. In that time no
+  background process came along to create this directory. Besides all
+  testing processing has terminated long ago.</li>
+
+  <li>The directory used to exist and some process, thinking it was
+  done with this, decided to get rid of the directory.</li>
+</ol>
+
+<p>Any ideas?</p>
+
+<?php copyright();?>
+</body>
+</html>
diff --git a/rantest/web/Rantest/ChangeLog1.0.php b/rantest/web/Rantest/ChangeLog1.0.php
new file mode 100644 (file)
index 0000000..0cdc0bb
--- /dev/null
@@ -0,0 +1,501 @@
+<?php
+////////////////////////////////////////////////////////////////////////////////
+//
+// File:       Changelog1.0.php
+// Revision:   1.0
+// Description:        Change log for Rantest 1.0
+// Author:     Andrew@ClearSCM.com
+// Created:    Mon Apr 28 15:20:06 MST 2008
+// Modified:   
+// Language:   PHP
+//
+// (c) Copyright 2008, General Dynamics, all rights reserved.
+//
+// All rights reserved except as subject to DFARS 252.227-7014 of contract
+// number CP02H8901N issued under prime contract N00039-04-C-2009.
+//
+// Warning: This document contains technical data whose export is restricted
+// by the Arms Export Control Act (Title 22, U.S.C., Sec 2751, et seq.) or the
+// Export Administration Act of 1979, as amended, Title, 50, U.S.C., App. 2401
+// et seq. Violations of these export laws are subject to severe criminal
+// penalties. Disseminate in accordance with provisions of DoD Directive
+// 5230.25.
+//
+////////////////////////////////////////////////////////////////////////////////
+$script = basename ($_SERVER["PHP_SELF"]);
+
+include_once "$_SERVER[DOCUMENT_ROOT]/php/Utils.php";
+include_once "$_SERVER[DOCUMENT_ROOT]/php/RantestDB.php";
+?>
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
+   "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+  <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+  <link rel="stylesheet" type="text/css" media="screen" href="/css/Testing.css">
+  <link rel="stylesheet" type="text/css" media="screen" href="/css/Tables.nohover.css">
+  <title>RANTEST: ChangeLog</title>
+</style>
+</head>
+
+<body>
+
+<?php print banner ();?>
+
+<h1 align=center>RANTEST 1.0 ChangeLog</h1>
+
+<ul>
+   <li><a href="#1.0.9">Version 1.0.9</a></li>
+
+   <li><a href="#1.0.8b">Version 1.0.8b</a></li>
+
+   <li><a href="#1.0.8a">Version 1.0.8a</a></li>
+
+   <li><a href="#1.0.8">Version 1.0.8</a></li>
+
+   <li><a href="#1.0.7">Version 1.0.7</a></li>
+
+   <li><a href="#1.0.6">Version 1.0.6</a></li>
+
+   <li><a href="#1.0.5d">Version 1.0.5d</a></li>
+
+   <li><a href="#1.0.5c">Version 1.0.5c</a></li>
+
+   <li><a href="#1.0.5b">Version 1.0.5b</a></li>
+
+   <li><a href="#1.0.5a">Version 1.0.5a</a></li>
+
+   <li><a href="#1.0.5">Version 1.0.5</a></li>
+
+   <li><a href="#1.0.4">Version 1.0.4</a></li>
+
+   <li><a href="#1.0.3b">Version 1.0.3b</a></li>
+
+   <li><a href="#1.0.3a">Version 1.0.3a</a></li>
+
+   <li><a href="#1.0.3">Version 1.0.3</a></li>
+
+   <li><a href="#1.0.2b">Version 1.0.2b</a></li>
+
+   <li><a href="#1.0.2a">Version 1.0.2a</a></li>
+
+   <li><a href="#1.0.2">Version 1.0.2</a></li>
+
+   <li><a href="#1.0.1">Version 1.0.1</a></li>
+
+   <li><a href="#1.0">Version 1.0 - First release!</a></li>
+
+</ul>
+
+<p>This is the ChangeLog for RANTEST for versions 1.0 and up.</p>
+
+<p>See also:</p>
+
+<ul>
+  <li><a href="ChangeLog0.9.php">RANTEST 0.9 ChangeLog</a></li>
+
+  <li><a href="ChangeLog1.1.php">RANTEST 1.1 ChangeLog</a></li>
+
+  <li><a href="ChangeLog.php">RANTEST 1.2 ChangeLog</a></li>
+</ul>
+
+<h2><a name="1.0.9">Version 1.0.9</a></h2><hr>
+
+<h3>East.pm:</h3>
+
+<ul>
+  <li>Fixed to properly check in CDR log files when -rfr is
+  specified.</li>
+</ul>
+
+<h2><a name="1.0.8b">Version 1.0.8b</a></h2><hr>
+
+<h3>East.pm:</h3>
+
+<ul>
+  <li>Fixed bug in handling of the case where the test case profile
+  was not found but we still go to collect logfiles and fail
+  badly. This is now handled better.</li>
+</ul>
+
+<h3>Web:</h3>
+
+<ul>
+  <li>Change suite report to be ordered in decending order on
+  start. Also durations aren't being properly computed when suite
+  crosses day boundary.</li>
+</ul>
+
+<h2><a name="1.0.8a">Version 1.0.8a</a></h2><hr>
+
+<h3>East.pm:</h3>
+
+<ul>
+  <li>Fixed bug where there were no PC Scanner logs but we were not
+  returning a proper status.</li>
+</ul>
+
+<h2><a name="1.0.8">Version 1.0.8</a></h2><hr>
+
+<h3>East.pm:</h3>
+
+<ul>
+  <li>Changed to support creating and collecting of PC Scanner logs if
+  rantvl was run and if we were running an RNC test</li>
+</ul>
+
+<h2><a name="1.0.7">Version 1.0.7</a></h2><hr>
+
+<h3>Rantest:</h3>
+
+<ul>
+  <li>Changed to list validations as they are executed in log output</li>
+
+  <li>Changed to fill in the path information for -config on aal2val
+  and tmival validations</li>
+</ul>
+
+<h3>East.pm:</h3>
+
+<ul>
+  <li>Changed to default timeout to 180 seconds for
+  mergeEAST2RNC.pl</li>
+</ul>
+
+<h2><a name="1.0.6">Version 1.0.6</a></h2><hr>
+
+<h3>Rantest:</h3>
+
+<ul>
+  <li>Added -feature parameter</li>
+
+  <li>Changed to enforce usage of a view context when running a .suite
+  file</li>
+
+</ul>
+
+<h3>East.pm:</h3>
+
+<ul>
+  <li>Changed connect to accept -feature parameter</li>
+</ul>
+
+<h3>Web</h3>
+
+<ul>
+  <li>Added DUT to TestHistory</li>
+</ul>
+
+<h2><a name="1.0.5d">Version 1.0.5d</a></h2><hr>
+
+<h3>East.pm:</h3>
+
+<ul>
+  <li>Changed to accept -timeout parameter for rantvl</li>
+</ul>
+
+<h2><a name="1.0.5c">Version 1.0.5c</a></h2><hr>
+
+<h3>East.pm:</h3>
+
+<ul>
+  <li>Changed to set the prompt (PS1) to something more
+  distinctive. This should fix the "That happened too quickly"
+  problem</li>
+</ul>
+
+<h2><a name="1.0.5b">Version 1.0.5b</a></h2><hr>
+
+<h3>Rantest:</h3>
+
+<ul>
+  <li>Now logs which view is in use for the test</li>
+</ul>
+
+<h3>East.pm:</h3>
+
+<ul>
+  <li>Added more verbose output when an scp fails</li>
+
+  <li>Changed to retry scp command if it fails</li>
+
+  <li>Fixed -rfr to use proper vob, nms_sim_log</li>
+</ul>
+
+<h2><a name="1.0.5a">Version 1.0.5a</a></h2><hr>
+
+<h3>Rantest:</h3>
+
+<ul>
+  <li>Fixed bug where rantest erroneously assumes there are
+  validations when there isn't.</li>
+</ul>
+
+<h3>Web:</h3>
+
+<ul>
+  <li>Fixed css image</li>
+</ul>
+
+<h2><a name="1.0.5">Version 1.0.5</a></h2><hr>
+
+<h3>Rantest:</h3>
+
+<ul>
+  <li>Changed some coloring.</li>
+
+  <li>Changed to report shell and rantvl to the database better.</li>
+
+  <li>Changed to call collectExtendedLogfile after each test
+  step.</li>
+
+  <li>Changed to fully qualify validations to point into the current
+  view instead of reling on the setting of PATH.</li>
+
+  <li>Moved special casing of the elock command closer to
+  GetOptions.</li>
+
+  <li>Moved running of validations to runTestFile so that a validation
+  failure will properly register that the test failed in the
+  database.</li>
+</ul>
+
+<h3>East.pm:</h3>
+
+<ul>
+  <li>Moved collection of TM500/NMS and CDR logfiles to
+  collectExtendedLogfile</li>
+
+  <li>Reverted making of baselines.readme to the old method. Calling
+  rebaseNonModifiables.pl, while the right way to go, kept causing
+  "NFS Stale File handle" messages. Will revert back to calling
+  rebaseNonModifiables.pl when Tom can change this to write to
+  stdout. When that happens we can write the file locally
+  (rebaseNonModifiables.pl had to run on cclinux, thus writing back to
+  NFS) and then check it in remotely.</li>
+
+  <li>Added collection of CDR log files.</li>
+</ul>
+
+<h3>Web:</h3>
+
+<ul>
+  <li>Recentered the caption on detailed test run page</li>
+</ul>
+
+<h2><a name="1.0.4">Version 1.0.4</a></h2><hr>
+
+<h3>Rantest:</h3>
+
+<ul>
+  <li>Created an array of validValidations</li>
+
+  <li>Added support for the following new validations</li>
+
+  <ol>
+    <li>bc_crc_parse.pl &lt;RBSLog.txt&gt;</li>
+
+    <li>pco.pl -f &lt;PCO...log&gt;</li>
+
+    <li>pco_mrach.pl -f &lt;PCO...log&gt;</li>
+  </ol>
+
+  <li>Fixed option processing so that extraneous parameters are now
+  caught.</li>
+</ul>
+
+<h3>East.pm:</h3>
+
+<ul>
+  <li>Removed HACK, COUGH, PHEWY!</li>
+
+  <li>Changed to report test step status correctly when there are
+  multiple logs</li>
+
+  <li>Fixed to return Success if exec shell was successful</li>
+
+  <li>Changed to use rebaseNonModifiables.pl -save to capture baselines</li>
+</ul>
+
+<h3>Web</h3>
+
+<ul>
+  <li>Now sorting by start time the Test Step report</li>
+
+  <li>Fixed versions line by centering and reformatting it</li>
+</ul>
+
+<h2><a name="1.0.3b">Version 1.0.3b</a></h2><hr>
+
+<h3>East.pm:</h3>
+
+<ul>
+  <li>Changed makeBaselinesReadme to use Tom's rebaseNonModifiables.pl
+  with a -save option. This allows us to later on use
+  rebaseNonModifiables.pl to restore the baseline configuration
+  easily.</li>
+</ul>
+
+<h3>Web:</h3>
+
+<ul>
+  <li>Added duration column to Test History report.</li>
+
+  <li>Added link for ranscrub log to home page</li>
+
+  <li>Reformatted and centered the versions line on Test Step Run
+  report.</li>
+</ul>
+
+<h2><a name="1.0.3a">Version 1.0.3a</a></h2><hr>
+
+<h3>East.pm:</h3>
+
+<ul>
+ <li>Changed to handle multiple logfiles</li>
+</ul>
+
+<h3>Web</h3>
+
+<ul>
+  <li>Changed select statement for VersionPerTest.php</li>
+</ul>
+
+<h2><a name="1.0.3">Version 1.0.3</a></h2><hr>
+
+<h3>Rantest:</h3>
+
+<ul>
+  <li>Added check to insure we are running on seast1</li>
+
+  <li>Added support for gathering of rantvl alarms</li>
+
+  <li>Changed validations to be combined under a directory called
+  Validations. rantest now creates log files under Validations and
+  also displays them to stdout.</li>
+
+  <li>Added shell option to exec to be able to do an arbitrary shell
+  command.</li>
+</ul>
+
+<h3>East:</h3>
+
+<ul>
+  <li>Added setRantvlStartTime method</li>
+
+  <li>Changed _mkDirElement to create the directory then call mkelem</li>
+
+  <li>Added code to collectLogFiles to get any alarms generated if
+  rantvl was started. East takes the setRantvlStartTime and the
+  current time to compute what the minute parameter to the moshell
+  command lgar needs then uses domsh to perform a lgar &lt;n&gt;m to
+  get a list of all alarms generated on the DUT in &lt;n&gt;m. This
+  output is redirected to the Rantvl log area named
+  [RBS|RNC]Alarms.txt.</li>
+</ul>
+
+<h3>Miscellaneous</h3>
+
+<ul>
+  <li>Updated the shared pswit authorized_keys file</li>
+</ul>
+
+<h2><a name="1.0.2b">Version 1.0.2b</a></h2><hr>
+
+<ul>
+  <li>Fixed bug with error handling</li>
+</ul>
+
+<h2><a name="1.0.2a">Version 1.0.2a</a></h2><hr>
+
+<ul>
+  <li>Added hotfix to fix up log files using Erik's
+  mergeEAST2RNC.pl.</li>
+
+  <li>Fixed bug in ranscrub where it calls an unknown subroutine
+  (scrub) instead of testLogsScrub. This is the first day we got to
+  the 45 day period where scrubbing, as opposed to just compressing,
+  has occurred.</li>
+
+  <li>More updates to RantestDesign.php</li>
+
+  <li>Renaming RantestDesign.php -> TestAutomationDesign.php</li>
+</ul>
+
+<h2><a name="1.0.2">Version 1.0.2</a></h2><hr>
+
+<ul>
+  <li>Fixed to check in parent directory when it was necessary to
+  create a new element.</li>
+</ul>
+
+<h2><a name="1.0.1">Version 1.0.2</a></h2><hr>
+
+<ul>
+  <li>Fixed donightly to use the 1123 view</li>
+
+  <li>Changed to check in newly created directory in _mkDirElement</li>
+</ul>
+
+<h2><a name="1.0">Version 1.0 - First release!</a></h2><hr>
+
+<p>While this is the first official release, it doesn't contain really
+ground breaking functionality. Just reaching a level of enough
+functionally complete code to be considered 1.0. We will obviously
+have bug patches and the like but by and large most of the
+functionality envisoned is coded.</p>
+
+<h3>Ranscrub:</h3>
+
+<ul>
+  <li>Added scrubbing for RantestDB. In the past this was stubbed
+  out. Now it actually scrubs various "run" tables like suiterun,
+  steprun and the testrun/testruns pair. Parameters are obtained from
+  and controlled by ../config/ranscrub.conf. Space savings reporting
+  is a little different for RantestDB. Instead we report the number of
+  entries scrubbed. This is the total of all of the run table entries
+  that were deleted. Not sure how helpful this figure really is except
+  to show that we are scrubbing something!</li>
+</ul>
+
+<h3>Rantest:</h3>
+
+<ul>
+  <li>Added support for new val_groups.pl</li>
+
+  <li>Changed to use .../simdev/test when looking for .test files
+  running from a .suite file.</li>
+
+  <li>Changed to prepend -p for valMsgs.pl with
+  .../simdev/msgdefs</li>
+
+  <li>Changed to set $_status{Failed} if a validation failed</li>
+</ul>
+
+<h3>East.pm:</h3>
+
+<ul>
+  <li>Formalized ls -t bug catching logging - for now</li>
+
+  <li>Fixed bug where a testCaseID directory element is created but
+  left checked out. We now check that in.</li>
+
+  <li>Added functionality to capture the baselines when we are doing
+  Run for Record and checking them into the testCaseID directory as
+  baselines.readme.</li>
+</ul>
+
+<h3>Web:</h3>
+
+<ul>
+  <li>Removed extraneous carriage returns from Testing.css</li>
+
+  <li>Added some additional styles for documentation</li>
+
+  <li>Added docs directory, link in Copyright and both
+  RantestDesign.php GuideToAutomatingTests.php</li>
+</ul>
+
+<?php copyright();?>
+</body>
+</html>
diff --git a/rantest/web/Rantest/ChangeLog1.1.php b/rantest/web/Rantest/ChangeLog1.1.php
new file mode 100644 (file)
index 0000000..c101197
--- /dev/null
@@ -0,0 +1,295 @@
+<?php
+////////////////////////////////////////////////////////////////////////////////
+//
+// File:       Changelog1.1.php
+// Revision:   1.1
+// Description:        Change log for rantest 1.1
+// Author:     Andrew@ClearSCM.com
+// Created:    Mon Apr 28 15:20:06 MST 2008
+// Modified:   
+// Language:   PHP
+//
+// (c) Copyright 2008, General Dynamics, all rights reserved.
+//
+// All rights reserved except as subject to DFARS 252.227-7014 of contract
+// number CP02H8901N issued under prime contract N00039-04-C-2009.
+//
+// Warning: This document contains technical data whose export is restricted
+// by the Arms Export Control Act (Title 22, U.S.C., Sec 2751, et seq.) or the
+// Export Administration Act of 1979, as amended, Title, 50, U.S.C., App. 2401
+// et seq. Violations of these export laws are subject to severe criminal
+// penalties. Disseminate in accordance with provisions of DoD Directive
+// 5230.25.
+//
+////////////////////////////////////////////////////////////////////////////////
+$script = basename ($_SERVER["PHP_SELF"]);
+include_once "$_SERVER[DOCUMENT_ROOT]/php/Utils.php";
+include_once "$_SERVER[DOCUMENT_ROOT]/php/RantestDB.php";
+?>
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
+   "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+  <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+  <link rel="stylesheet" type="text/css" media="screen" href="/css/Testing.css">
+  <link rel="stylesheet" type="text/css" media="screen" href="/css/Tables.nohover.css">
+  <title>RANTEST: ChangeLog</title>
+</style>
+</head>
+
+<body>
+
+<?php print banner ()?>
+
+<h1 align=center>RANTEST 1.1 ChangeLog</h1>
+
+<ul>
+   <li><a href="#1.1.7">Version 1.1.7</a></li>
+
+   <li><a href="#1.1.6">Version 1.1.6</a></li>
+
+   <li><a href="#1.1.5">Version 1.1.5</a></li>
+
+   <li><a href="#1.1.4">Version 1.1.4</a></li>
+
+   <li><a href="#1.1.3">Version 1.1.3</a></li>
+
+   <li><a href="#1.1.2">Version 1.1.2</a></li>
+
+   <li><a href="#1.1.1">Version 1.1.1</a></li>
+
+   <li><a href="#1.1.0">Version 1.1.0</a></li>
+</ul>
+
+<p>This is the ChangeLog for RANTEST for versions 1.1 and up.</p>
+
+<p>See also:</p>
+
+<ul>
+  <li><a href="ChangeLog0.9.php">RANTEST 0.9 ChangeLog</a></li>
+
+  <li><a href="ChangeLog1.0.php">RANTEST 1.0 ChangeLog</a></li>
+
+  <li><a href="ChangeLog.php">RANTEST 1.2 ChangeLog</a></li>
+</ul>
+
+<h2><a name="1.1.7">Version 1.1.7</a></h2><hr>
+
+<h3>East.pm:</h3>
+
+<ul>
+  <li>Changed rantest_build2_log -> rantest_build3_log</li>
+</ul>
+
+<h2><a name="1.1.6">Version 1.1.6</a></h2><hr>
+
+<h3>East.pm:</h3>
+
+<ul>
+  <li>Changed to properly set TM500_VIEW and NMS_VIEW into the
+  environment of the blade</li>
+</ul>
+
+<h2><a name="1.1.5">Version 1.1.5</a></h2><hr>
+
+<h3>East.pm:</h3>
+
+<ul>
+  <li>Changed to display error where FEATURE is not set</li>
+</ul>
+
+<h2><a name="1.1.4">Version 1.1.4</a></h2><hr>
+
+<h3>Rantest:</h3>
+
+<ul>
+  <li>Changed to not use SITE_PERLLIB anymore</li>
+
+  <li>No longer calls fixUpLogs if rantvl has not been run</li>
+
+  <li>Handles -regression and options to tests in suite files
+  better</li>
+</ul>
+
+<h3>East:</h3>
+
+<ul>
+  <li>Rantest no longer assumes that the failed execution of the
+  "shell" command has output to explain the error</li>
+
+  <li>The shell subrouting now properly returns status</li>
+</ul>
+
+<h2><a name="1.1.3">Version 1.1.3</a></h2><hr>
+
+<h3>Web:</h3>
+
+<ul>
+  <li>Changed to sink rantest under it's own directory</li>
+</ul>
+
+<h2><a name="1.1.2">Version 1.1.2</a></h2><hr>
+
+<h3>Rantest:</h3>
+
+<ul>
+  <li>Changed to check and set PATH when rantest is running in suite
+  mode</li>
+</ul>
+
+<h2><a name="1.1.1">Version 1.1.1</a></h2><hr>
+
+<h3>Rantest:</h3>
+
+<ul>
+  <li>Changed to collect extended logfile even in cases where -timeout
+  is defaulted</li>
+</ul>
+
+<h2><a name="1.1.0">Version 1.1.0</a></h2><hr>
+
+<h3>Rantest:</h3>
+
+<ul>
+  <li>Changed to support new validation API described below</li>
+</ul>
+
+<h3>New Validation API</h3>
+
+<p>It was decided to get rantest out of the business of interpreting
+validation command lines and instead simply offer <i>variables</i>
+which would be available for test writers to use. This is viewed as a
+good thing because future validations and validators can be written
+and rantest need not be modified to become aware of them. Validations
+or val lines become not much different than exec shell lines.</p>
+
+<p>Rantest therefore supports the following pseudo (and non-pseudo)
+variables which can be used in val lines (or any other lines for that
+matter):</p>
+
+<ul>
+  <li>Any variable in your env(1). (e.g. $USER)</li>
+
+  <li>$logpath: Absolute path into the "testlogs" area where logfiles
+  will be written.</li>
+
+  <li>$msgdefs: Absolute path into the simdev vob/msgdefs
+  directory.</li>
+
+  <li>$validation: Absolute path into the simdev vob/validation
+  directory.</li>
+
+  <li>$view: Absolute path to your view</li>
+
+  <LI>Others?</li>
+</ul>
+
+<p><u>This, of course, means that val lines have to be 
+changed to use the above variables to specify their missing parts.</u> Thus a 
+previous val line of simply:</p>
+
+<div class=code><pre>
+val:  valMsgs.pl -p MyConfig.txt
+</pre></div>
+
+<p>would need to be changed to:</p>
+
+<div class=code><pre>
+val:  valMsgs.pl -p $msgdefs/MyConfig.txt -l $logpath/Rantvl/RNCLog.txt
+</pre></div>
+
+<p>Note that now the test writer needs to have the valMsgs.pl line
+specify RNCLog.txt or RBSLog.txt depending on what type of test they
+are doing. IOW rantest no longer is charged with figuring this
+out.</p>
+
+<blockquote>
+  Note: Rantest validations are currently not in wide use so now is a
+  good time to implement this change in the API.
+</blockquote>
+
+<p>Similarly, an aal2val line may change from:</p>
+
+<div class=code><pre>
+val:  aal2val -config My.conf
+</pre></div>
+
+<p>to:</p>
+
+<div class=code><pre>
+val:  aal2val -config $validations/My.conf -f $logpath/EASTLogs/Server_Logs/rnc_aal2.log
+</pre></div>
+
+<h3>Dynamic filesets:</h3>
+
+<p>Some validations run on a fileset which is not known to the test
+writer at the time s/he is writing the test. In such cases you can
+designate a fileset that rantest will expand and replace with a comma
+separated list full pathnames $[<i>dynamic fileset</i>]. So, for
+example, $[/tmp/*] would return a comma separated list of all files
+under /tmp. If only a portion of the dynamic file is required you can
+include an array reference or a slice. For example:</p>
+
+<div class=code><pre>
+val: &lt;<i>validation script</i>&gt; -f $[$logpath/foo*.log][0]
+val: &lt;<i>validation script</i>&gt; -f $[$logpath/foo*.log][1..2]
+</pre></div>
+
+<p>This would tell rantest to expand $logpath first, then expand the
+fileset, then take the first (0th) entry file list for the first
+validation, or create a comma separated list of the second and third
+pathnames from the file list second validation. So, assuming there
+were logfiles with the names foo[1-3].log, rantest would expand the
+above giving us:</p>
+
+<div class=code><pre>
+val: &lt;<I>validation script</I>&gt; -f /east/seast1/testlogs/testcase1/rnc5/20080917@14;38:03/foo1.log
+val: &lt;<I>validation script</I>&gt; -f /east/seast1/testlogs/testcase1/rnc5/20080917@14;38:03/foo2.log,/east/seast1/testlogs/testcase1/rnc5/20080917@14;38:03/foo3.log
+</pre></div>
+
+<h3>Notes</h3>
+
+<ol>
+  <li>Validations may need to be recorded to expect multiple
+  parameters. For example, -f above for &lt;<i>validation
+  script</i>&gt; needs to accept multple values for -f (Getopt::Long
+  handles this fairly nicely however &lt;<i>validation
+  script</i>&gt;'s logic may need to change somewhat).</li>
+
+  <li>When using filesets it's possible that empty lists will be returned. For 
+  example, if $[$logpath/foo*.log] evaluates to no files you'll get back 
+  nothing. Additionally $[$logpath/foo*.log][10..12] will return nothing 
+  assuming there are no files in the 10..12 slice.</li>
+</ol>
+
+<p>Our final example is tmiVal, which is using multiple config files
+against a dynamic fileset:</p>
+
+<div class=code><pre>
+val:  tmiVal -config config1,config2,config3 -configbase $validations \
+  -logbase $logpath/TM500Logs -logfiles ${$logbase/TM500Logs/TMI.dl.*}
+</pre></div>
+
+<h3>Notes:</h3>
+
+<ol>
+  <li>tmiVal needs to be re-written to handle these new options</li>
+
+  <li>-configbase is applied to each leaf node under -config making
+  $configbase/config1, $configbase/config2 and $configbase/config3. It
+  is tmiVal - not rantest - which applies this rule.</li>
+
+  <li>Similarly $logbase applies to @logfiles like $configbase</li>
+
+  <li>There is no guarantee that there will only be 3 TMI.dl.*
+  logfiles nor is the ordering of the fileset guaranteed, save for the
+  natural ordering of ls(1).</li>
+
+  <li>The syntax of \ to indicate continue this line is <b>not</b>
+  supported by rantest and is used above only to enhance
+  readability.</li>
+</ol>
+
+<?php copyright();?>
+</body>
+</html>
diff --git a/rantest/web/Rantest/FailureAnalysis.php b/rantest/web/Rantest/FailureAnalysis.php
new file mode 100644 (file)
index 0000000..2b5750b
--- /dev/null
@@ -0,0 +1,304 @@
+<?php
+////////////////////////////////////////////////////////////////////////////////
+//
+// File:       FailureAnalysis.php
+// Revision:   1.2
+// Description:        Produce a report showing an analysis of failures for a day
+// Author:     Andrew@ClearSCM.com
+// Created:    Mon Apr 28 15:20:06 MST 2008
+// Modified:   
+// Language:   PHP
+//
+// (c) Copyright 2008, General Dynamics, all rights reserved.
+//
+// All rights reserved except as subject to DFARS 252.227-7014 of contract
+// number CP02H8901N issued under prime contract N00039-04-C-2009.
+//
+// Warning: This document contains technical data whose export is restricted
+// by the Arms Export Control Act (Title 22, U.S.C., Sec 2751, et seq.) or the
+// Export Administration Act of 1979, as amended, Title, 50, U.S.C., App. 2401
+// et seq. Violations of these export laws are subject to severe criminal
+// penalties. Disseminate in accordance with provisions of DoD Directive
+// 5230.25.
+//
+////////////////////////////////////////////////////////////////////////////////
+$script = basename ($_SERVER["PHP_SELF"]);
+
+include_once "$_SERVER[DOCUMENT_ROOT]/php/Utils.php";
+include_once "$_SERVER[DOCUMENT_ROOT]/php/RantestDB.php";
+
+$day           = $_REQUEST["day"];
+$user          = $_REQUEST["user"];
+
+$action                = (empty ($_REQUEST["action"]))    ? "Report"     : $_REQUEST["action"];
+$sortBy                = (empty ($_REQUEST["sortBy"]))    ? "Date"       : $_REQUEST["sortBy"];
+$direction     = (empty ($_REQUEST["direction"])) ? "ascending"  : $_REQUEST["direction"];
+
+// Sorting functions
+function sortTime ($a, $b) {
+  global $direction;
+
+  return cmpStr ($a, $b, "Time", $direction);
+} // sortTime
+
+function getData ($day) {
+  global $sortBy;
+
+  $data = getFailures ($day);
+
+  // Sort data
+  if ($sortBy == "Testcase") {
+    uasort ($data, "sortTestcase");
+  } else if ($sortBy == "Unit") {
+    uasort ($data, "sortUnit");
+  } else {
+    uasort ($data, "sortTime");
+  } // if
+
+  return $data;
+} // getData
+
+function createCSV ($day) {
+  $data = getData ($day);
+
+  // Title line
+  $csv  = "Failure Analysis for $day\n";
+
+  // Create header line
+  $firstTime = true;
+
+  foreach ($data[0] as $key => $value) {
+    // Skip "hidden" fields - fields beginning with "_"
+    if (preg_match ("/^_/", $key) == 1) {
+      continue;
+    } // if
+
+    if ($key == "Failures") {
+      $csv .= "\n";
+      $csv .= ",Teststep,Failure Reason";
+      
+      continue;
+    } // if
+
+    if (!$firstTime) {
+      $csv .= ",";
+    } else {
+      $firstTime = false;
+    } // if
+
+    $csv .= "\"$key\"";
+  } // foreach
+
+  $csv .= "\n";
+
+  // Data lines
+  foreach ($data as $entry) {
+    $firstTime = true;
+
+    foreach ($entry as $key => $value) {
+      // Skip "hidden" fields - fields beginning with "_"
+      if (preg_match ("/^_/", $key) == 1) {
+       continue;
+      } // if
+
+      if ($key == "Failures") {
+       $csv .= "\n";
+
+       foreach ($value as $key => $failure) {
+         $csv .= ",$failure[Step],$failure[Reason]\n";
+       } // foreach
+
+       continue;
+      } // if
+
+      if (!$firstTime) {
+       $csv .= ",";
+      } else {
+       $firstTime = false;
+      } // if
+
+      $csv .= "\"$value\"";
+    } // foreach
+
+    $csv .= "\n";
+  } // foreach
+
+  return $csv;
+} // createCSV
+
+function exportStats ($day) {
+  $filename = "Test Statistics." . $day . ".csv";
+
+  header ("Content-Type: application/octect-stream");
+  header ("Content-Disposition: attachment; filename=\"$filename\"");
+
+  print createCSV ($day);
+
+  exit;
+} // exportStats
+
+function createHeader ($day) {
+  $header = <<<END
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
+   "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+  <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+  <link rel="stylesheet" type="text/css" media="screen" href="/css/Testing.css">
+  <link rel="stylesheet" type="text/css" media="screen" href="/css/Tables.css">
+  <title>Failure Analysis for $day</title>
+<script language="javascript">
+function ChangeDay (day, script) {
+  window.location = script + "?day=" + day;
+} // ChangeDay
+</script>
+<body>
+END;
+
+  $header .= banner ();
+
+  return $header;
+} // createHeader
+
+function createPage ($day, $forEmail = false, $message = "") {
+  global $direction, $sortBy, $direction, $script;
+
+  $data                = getData ($day);
+  $dateDropdown = reportDateDropdown ();
+  $row_nbr     = 0;
+
+  if (!$forEmail) {
+    $page .= "<h1 align=\"center\">Failure Analysis for $dateDropdown</h1>";
+
+    // Flip direction
+    $direction = ($direction == "ascending") ? "descending" : "ascending";
+    $urlParms  = "$script?day=$day&action=$action&direction=$direction&sortBy";
+
+    if ($sortBy == "Testcase") {
+      $testcaseDirection = ($direction == "ascending") 
+       ? "<img src=/images/down.png border=0>"
+       : "<img src=/images/up.png border=0>";
+    } else if ($sortBy == "Unit") {
+      $unitDirection = ($direction == "ascending") 
+       ? "<img src=/images/down.png border=0>"
+       : "<img src=/images/up.png border=0>";
+    } else {
+      $timeDirection = ($direction == "ascending") 
+       ? "<img src=/images/down.png border=0>"
+       : "<img src=/images/up.png border=0>";
+    } // if
+
+    if (isset ($message)) {
+      $page .= "<div align=center>$message</div>";
+    } // if
+  } else {
+    $page .= "<h1 align=\"center\">Failure Analysis for $day</h1>";
+  } // if
+
+  $page .= <<<END
+<table align=center width=90%>
+  <thead>
+END;
+
+  if (!$forEmail) {
+    $page .= <<<END
+    <tr>
+      <th class="clear" align="left"><a href="$script?action=Export&day=$day"><input type="submit" value="Export to CSV"></a></th>
+      <th class="clear" colspan="2"
+ align="right"><form action="$script?action=Mail&day=$day" method="post">
+END;
+
+    $page .= emailUsersDropdown ();
+    $page .= <<<END
+        </select>
+        <input type="submit" value="Send"></form>
+      </th>
+    </tr>
+END;
+  } // if
+
+  $page .= <<<END
+    <tr>
+      <th class=left><a href="$urlParms=Testcase">Testcase&nbsp;$testcaseDirection</a></th>
+      <th><a href="$urlParms=Unit">Unit&nbsp;$unitDirection</a></th>
+      <th class=right><a href="$urlParms=Time">Time&nbsp;$timeDirection</a></th>
+    </tr>
+    <tr>
+      <th width="100px">&nbsp;</th>
+      <th>Teststep</th>
+      <th>Failure Reason</th>
+    </tr>
+  </thead>
+  <tbody>
+END;
+
+  foreach ($data as $result) {
+    $row_color = " class=other";
+
+    $unit = ($result["Unit"]) ? $result["Unit"] : "";
+    $time = ($result["Time"]) ? $result["Time"] : "";
+
+    $page .= <<<END
+    <tr $row_color>
+      <td><a href="rantest.php?testName=$result[Testcase]&runid=$result[_runid]&date=$result[Date]">$result[Testcase]</a></td>
+      <td align=right>$unit</td>
+      <td align=right>$time</td>
+    </tr>
+END;
+
+    foreach ($result["Failures"] as $failure) {
+      $step    = $failure["Step"];
+      $reason  = $failure["Reason"];
+      $page .= <<<END
+    <tr class="failure">
+      <td>&nbsp;</td>
+      <td>$step</td>
+      <td>$reason</td>
+    </tr>
+END;
+    } // foreach
+  } // foreach
+
+  $page .= <<<END
+  </tbody>
+</table>
+END;
+
+  return $page;
+} // createPage
+
+function displayReport ($day, $message = "") {
+  print createHeader   ($day);
+  print createPage     ($day, false, $message);
+
+  copyright ();
+} // displayReport
+
+function mailFailureAnalysisReport ($day, $pnbr, $username) {
+  $subject     = "Failure Analysis for $day";
+  $body                = createPage ($day, true);
+  $filename    = "FailureAnalysis.$day.csv";
+  $attachment  = createCSV ($day);
+
+  return mailReport ($pnbr, $username, $subject, $body, $filename, $attachment);
+} // mailFailureAnalysisReport
+
+openDB ();
+
+switch ($action) {
+  case "Export":
+    exportStats ($day);
+    break;
+
+  case "Mail":
+    list ($pnbr, $username) = explode (":", $user);
+    displayReport ($day, mailFailureAnalysisReport ($day, $pnbr, $username));
+    break;
+
+  default:
+    displayReport ($day);
+    break;
+} // switch  
+?>
+</body>
+</html>
diff --git a/rantest/web/Rantest/GraphStats.php b/rantest/web/Rantest/GraphStats.php
new file mode 100644 (file)
index 0000000..bcff64e
--- /dev/null
@@ -0,0 +1,100 @@
+<?php
+////////////////////////////////////////////////////////////////////////////////
+//
+// File:       GraphStats.php
+// Revision:   1.2
+// Description:        Produce a graph showing number of tests passed/failed for a
+//             date range.
+// Author:     Andrew@ClearSCM.com
+// Created:    Mon Apr 28 15:20:06 MST 2008
+// Modified:   
+// Language:   PHP
+//
+// (c) Copyright 2008, General Dynamics, all rights reserved.
+//
+// All rights reserved except as subject to DFARS 252.227-7014 of contract
+// number CP02H8901N issued under prime contract N00039-04-C-2009.
+//
+// Warning: This document contains technical data whose export is restricted
+// by the Arms Export Control Act (Title 22, U.S.C., Sec 2751, et seq.) or the
+// Export Administration Act of 1979, as amended, Title, 50, U.S.C., App. 2401
+// et seq. Violations of these export laws are subject to severe criminal
+// penalties. Disseminate in accordance with provisions of DoD Directive
+// 5230.25.
+//
+////////////////////////////////////////////////////////////////////////////////
+$script = basename ($_SERVER["PHP_SELF"]);
+$inc   = $_SERVER["DOCUMENT_ROOT"];
+
+include_once "$inc/php/Utils.php";
+include_once "$inc/php/RantestDB.php";
+
+include_once ("$inc/pChart/pData.class");
+include_once ("$inc/pChart/pChart.class");
+
+$start = $_REQUEST["start"];
+$end   = $_REQUEST["end"];
+$type  = $_REQUEST["type"];
+
+$debug;
+
+function mydebug ($msg) {
+  $debug = fopen ("/tmp/debug.log", "a");
+
+  fwrite ($debug, "$msg\n");
+} // mydebug
+
+// Sorting functions
+function sortByDate ($a, $b) {
+  return strcmp ($a["Date"], $b["Date"]);
+} // sortByDate
+
+openDB ();
+
+$data = getStatus ($start, $end, $type);
+
+usort ($data, "sortByDate");
+
+$fonts = "$inc/Fonts";
+
+// Dataset definition 
+$DataSet = new pData;
+
+foreach ($data as $result) {
+  $reportDate = YMD2MDY ($result["Date"]);
+
+//  mydebug ("$reportDate Success $result[Success] Failure $result[Failure]");
+
+  $DataSet->AddPoint ($result["Success"], "Passed",    $reportDate);
+  $DataSet->AddPoint ($result["Failure"], "Failed");
+} // foreach
+
+$DataSet->AddAllSeries ();
+$DataSet->SetAbsciseLabelSerie ();
+
+$DataSet->SetSerieName ("Passed", "Passed");
+$DataSet->SetSerieName ("Failed", "Failed");
+
+// Initialise the graph
+$Test = new pChart (700, 280);
+$Test->drawGraphAreaGradient (100, 150, 175, 100, TARGET_BACKGROUND);
+$Test->setFontProperties ("$fonts/tahoma.ttf", 8);
+$Test->setGraphArea (50, 30, 680, 200);
+$Test->drawRoundedRectangle (5, 5, 695, 275, 5, 230, 230, 230);
+$Test->drawGraphAreaGradient (162, 183, 202, 50);
+$Test->drawScale ($DataSet->GetData (), $DataSet->GetDataDescription (), SCALE_ADDALL, 200, 200, 200, true, 70, 2, true);
+$Test->drawGrid (4, true, 230, 230, 230, 50);
+
+// Draw the 0 line
+$Test->setFontProperties ("$fonts/tahoma.ttf", 6);
+$Test->drawTreshold (0, 143, 55, 72, true, true);
+
+// Draw the bar graph
+$Test->drawStackedBarGraph ($DataSet->GetData (), $DataSet->GetDataDescription (), 75);
+
+// Finish the graph
+$Test->setFontProperties ("$fonts/tahoma.ttf",8);
+$Test->drawLegend (610, 35, $DataSet->GetDataDescription (), 130, 180, 205);
+$Test->setFontProperties ("$fonts/tahoma.ttf", 10);
+$Test->drawTitle (50, 22, "Test Metrics ($type)", 255, 255, 255, 675);
+$Test->Stroke ();
diff --git a/rantest/web/Rantest/TestHistory.php b/rantest/web/Rantest/TestHistory.php
new file mode 100644 (file)
index 0000000..4db86fc
--- /dev/null
@@ -0,0 +1,399 @@
+<?php
+////////////////////////////////////////////////////////////////////////////////
+//
+// File:       TestHistory.php
+// Revision:   1.2
+// Description:        Produce a historical report about a testcase
+// Author:     Andrew@ClearSCM.com
+// Created:    Mon Apr 28 15:20:06 MST 2008
+// Modified:   
+// Language:   PHP
+//
+// (c) Copyright 2008, General Dynamics, all rights reserved.
+//
+// All rights reserved except as subject to DFARS 252.227-7014 of contract
+// number CP02H8901N issued under prime contract N00039-04-C-2009.
+//
+// Warning: This document contains technical data whose export is restricted
+// by the Arms Export Control Act (Title 22, U.S.C., Sec 2751, et seq.) or the
+// Export Administration Act of 1979, as amended, Title, 50, U.S.C., App. 2401
+// et seq. Violations of these export laws are subject to severe criminal
+// penalties. Disseminate in accordance with provisions of DoD Directive
+// 5230.25.
+//
+////////////////////////////////////////////////////////////////////////////////
+$script = basename ($_SERVER["PHP_SELF"]);
+
+include_once "$_SERVER[DOCUMENT_ROOT]/php/Utils.php";
+include_once "$_SERVER[DOCUMENT_ROOT]/php/RantestDB.php";
+
+$testcase      = $_REQUEST["testcase"];
+$user          = $_REQUEST["user"];
+
+$build         = (empty ($_REQUEST["build"]))     ? "*"         : $_REQUEST["build"];
+$level         = (empty ($_REQUEST["level"]))     ? "*"         : $_REQUEST["level"];
+$DUT           = (empty ($_REQUEST["DUT"]))       ? "*"         : $_REQUEST["DUT"];
+$test          = (empty ($_REQUEST["test"]))      ? "*"         : $_REQUEST["test"];
+
+$action                = (empty ($_REQUEST["action"]))    ? "Report"     : $_REQUEST["action"];
+$type          = (empty ($_REQUEST["type"]))      ? "All"        : $_REQUEST["type"];
+$sortBy                = (empty ($_REQUEST["sortBy"]))    ? "Date"       : $_REQUEST["sortBy"];
+$direction     = (empty ($_REQUEST["direction"])) ? "descending" : $_REQUEST["direction"];
+
+$historyFor    = setTestcase ($testcase);
+
+// Sorting functions
+function sortDUT ($a, $b) {
+  global $direction;
+
+  return cmpStr ($a, $b, "DUT", $direction);
+} // sortDUT
+
+function sortAvgRunTime ($a, $b) {
+  global $direction;
+
+  return cmpNbr ($a, $b, "AvgRunTime", $direction);
+} // sortAvgRunTime
+
+function getData ($testcase) {
+  global $sortBy;
+
+  $data = getTestHistory ($testcase);
+
+  // Sort data
+  if ($sortBy == "Passed") {
+    uasort ($data, "sortPassed");
+  } elseif ($sortBy == "Failed") {
+    uasort ($data, "sortFailed");
+  } elseif ($sortBy == "Total") {
+    uasort ($data, "sortTotal");
+  } elseif ($sortBy == "AvgRunTime") {
+    uasort ($data, "sortAvgRunTime");
+  } elseif ($sortBy == "DUT") {
+    uasort ($data, "sortDUT");
+  } elseif ($sortBy == "Type") {
+    uasort ($data, "sortType");
+  } elseif ($sortBy == "Start") {
+    uasort ($data, "sortStart");
+  } elseif ($sortBy == "End") {
+    uasort ($data, "sortEnd");
+  } elseif ($sortBy == "Duration") {
+    uasort ($data, "sortDuration");
+  } elseif ($sortBy == "Status") {
+    uasort ($data, "sortStatus");
+  } else {
+    uasort ($data, "sortTestcase");
+  } // if    
+
+  return $data;
+} // getData
+
+function createHeader () {
+  global $historyFor;
+
+  $header = <<<END
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
+   "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+  <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+  <link rel="stylesheet" type="text/css" media="screen" href="/css/Testing.css">
+  <link rel="stylesheet" type="text/css" media="screen" href="/css/Tables.css">
+  <title>Test history for $historyFor</title>
+</head>
+
+<body>
+END;
+
+  $header .= banner ();
+  $header .= <<<END
+<h1 align="center">Test history for $historyFor</h1>
+END;
+
+  return $header;
+} // createHeader
+
+function createPage ($testcase, $forEmail = false, $message = "") {
+  global $webdir, $direction, $sortBy, $script;
+
+  $data = getData ($testcase);
+
+  if (!$forEmail) {
+    // Flip direction
+    $direction = ($direction == "ascending") ? "descending" : "ascending";
+
+    if ($sortBy == "DUT") {
+      $DUTDirection = ($direction == "ascending") 
+       ? "<img src=/images/down.png border=0>"
+       : "<img src=/images/up.png border=0>";
+    } elseif ($sortBy == "Type") {
+      $typeDirection = ($direction == "ascending") 
+       ? "<img src=/images/down.png border=0>"
+       : "<img src=/images/up.png border=0>";
+    } elseif ($sortBy == "Start") {
+      $startDirection = ($direction == "ascending") 
+       ? "<img src=/images/down.png border=0>"
+       : "<img src=/images/up.png border=0>";
+    } elseif ($sortBy == "End") {
+      $endDirection = ($direction == "ascending") 
+       ? "<img src=/images/down.png border=0>"
+       : "<img src=/images/up.png border=0>";
+    } elseif ($sortBy == "Duration") {
+      $durationDirection = ($direction == "ascending") 
+       ? "<img src=/images/down.png border=0>"
+       : "<img src=/images/up.png border=0>";
+    } elseif ($sortBy == "Status") {
+      $statusDirection = ($direction == "ascending") 
+       ? "<img src=/images/down.png border=0>"
+       : "<img src=/images/up.png border=0>";
+    } elseif ($sortBy == "Passed") {
+      $passedDirection = ($direction == "ascending") 
+       ? "<img src=/images/down.png border=0>"
+       : "<img src=/images/up.png border=0>";
+    } elseif ($sortBy == "Failed") {
+      $failedDirection = ($direction == "ascending") 
+       ? "<img src=/images/down.png border=0>"
+       : "<img src=/images/up.png border=0>";
+    } elseif ($sortBy == "Total") {
+      $totalDirection = ($direction == "ascending") 
+       ? "<img src=/images/down.png border=0>"
+       : "<img src=/images/up.png border=0>";
+    } elseif ($sortBy == "AvgRunTime") {
+      $avgRunTimeDirection = ($direction == "ascending") 
+       ? "<img src=/images/down.png border=0>"
+       : "<img src=/images/up.png border=0>";
+    } else {
+      $testcaseDirection = ($direction == "ascending") 
+       ? "<img src=/images/down.png border=0>"
+       : "<img src=/images/up.png border=0>";
+    } // if
+      
+    if (isset ($message)) {
+      $page .= "<div align=center>$message</div>";
+    } // if
+  } // if
+
+  $page .= <<<END
+<table align=center>
+  <thead>
+END;
+
+  if (!$forEmail) {
+    if (isset ($testcase)) {
+      $page .= <<<END
+    <tr>
+      <th class=clear align="left" colspan="2"><a href="$script?action=Export&testcase=$testcase"><input type="submit" value="Export to CSV"></a></th>
+      <th class=clear align="right" colspan="7"><form action="$script?action=Mail&testcase=$testcase" method="post">
+END;
+    } else {
+      $page .= <<<END
+    <tr>
+      <th class=clear align="left" colspan="2"><a href="$script?action=Export&testcase=$testcase"><input type="submit" value="Export to CSV"></a></th>
+      <th class=clear align="right" colspan="4"><form action="$script?action=Mail&testcase=$testcase" method="post">
+END;
+    } // if
+
+    $page .= emailUsersDropdown ();
+    $page .= <<<END
+        </select>
+        <input type="submit" value="Send"></form>
+      </th>
+    </tr>
+END;
+  } // if
+
+  if (isset ($testcase)) {
+    $urlParms  = "$script?testcase=$testcase&action=$action&direction=$direction&sortBy";
+    $page .= <<<END
+    <tr>
+      <th class=left>#</th>
+      <th><a href=$urlParms=Testcase>Testcase&nbsp;$testcaseDirection</a></th>
+      <th><a href=$urlParms=DUT>DUT&nbsp;$DUTDirection</a></th>
+      <th><a href=$urlParms=Type>Type&nbsp;$typeDirection</a></th>
+      <th><a href=$urlParms=Start>Start&nbsp;$startDirection</a></th>
+      <th><a href=$urlParms=End>End&nbsp;$endDirection</a></th>
+      <th><a href=$urlParms=Duration>Duration&nbsp;$durationDirection</a></th>
+      <th>Logs</th>
+      <th class=right><a href=$urlParms=Status>Status&nbsp;$statusDirection</a></th> 
+END;
+  } else {
+    $urlParms  = "$script?build=$build&level=$level&DUT=$DUT&test=$test&action=$action&direction=$direction&sortBy";
+    $page .= <<<END
+    <tr>
+      <th class=left>#</th>
+      <th><a href=$urlParms=Testcase>Testcase&nbsp;$testcaseDirection</a></th>
+      <th><a href=$urlParms=Passed>Passed&nbsp;$passedDirection</a></th>
+      <th><a href=$urlParms=Failed>Failed&nbsp;$failedDirection</a></th>
+      <th><a href=$urlParms=Total>Total&nbsp;$totalDirection</a></th>
+      <th class=right><a href=$urlParms=AvgRunTime>Avg Run Time&nbsp;$avgRunTimeDirection</a></th>
+END;
+  } // if
+
+  $page .= <<<END
+    </tr>
+  </thead>
+  <tbody>
+END;
+
+  $total_passed =
+  $total_failed =
+  $total_total = 0;
+
+  foreach ($data as $line) {
+    $row_nbr++;
+
+    if (isset ($testcase)) {
+      $class   = SetRowColor ($line["Status"]);
+      $status  = colorResult ($line["Status"]);
+      $date    = YMD2MDY (substr ($line["Start"], 0, 10));
+      $duration        = FormatDuration ($line["Duration"]);
+      $logs    = logs ($line["_eastlogs"]);
+
+      $page .= <<<END
+      <tr $class>
+        <td align=center>$row_nbr</td>
+        <td><a href="$webdir/rantest.php?testName=$line[Testcase]&runid=$line[_runid]&date=$date">$line[Testcase]</a></td>
+        <td align=center>$line[DUT]</td>
+        <td align=center>$line[Type]</td>
+        <td align=center>$line[Start]</td>
+        <td align=center>$line[End]</td>
+        <td>$duration</td>
+        <td align=center>$logs</td>
+        <td>$status</td>
+      </tr>
+END;
+    } else {
+      $row_color = ($row_nbr % 2 == 0) ? " class=other" : " class=white";
+
+      $page .= <<<END
+        <tr $row_color>
+          <td align=center>$row_nbr</td>
+          <td><a href="$script?testcase=$line[Testcase]">$line[Testcase]</a></td>
+          <td align=right>$line[Passed]</td>
+          <td align=right>$line[Failed]</td>
+          <td align=right>$line[Total]</td>
+         <td align=right>
+END;
+      $page .= FormatDuration ($line[AvgRunTime]);
+      $page .= "</td></tr>";
+      $total_passed    += $line["Passed"];
+      $total_failed    += $line["Failed"];
+      $total_total     += $line["Total"];
+    } // if
+  } // foreach  
+
+  // Print total
+  if (empty ($testcase)) {
+    $page .= <<<END
+  <tfoot>
+    <tr $row_color>
+      <th colspan=2>Total</th>
+      <th align=right>$total_passed</th>
+      <th align=right>$total_failed</th>
+      <th align=right>$total_total</th>
+      <th>&nbsp;</th>
+    </tr>
+  </tfoot>
+END;
+  } // if
+
+  $page .= <<<END
+  </tbody>
+</table>
+END;
+
+  return $page;
+} // createPage
+
+function exportTestHistoryCSV ($testcase) {
+  global $historyFor;
+
+  if (isset ($testcase)) {
+    $title     = "Test History for $historyFor";
+    $filename  = "Test History." . $testcase . ".csv";
+  } else {
+    $title     = "Test History for All Tests";
+    $filename  = "Test History.All Tests.csv";
+  } // if
+
+  // Protect $filename from wildcards
+  $filename = preg_replace ("/\*/", "%", $filename);
+
+  header ("Content-Type: application/octect-stream");
+  header ("Content-Disposition: attachment; filename=\"$filename\"");
+
+  print exportCSV (getData ($testcase), $title);
+
+  exit;
+} // exportTestHistoryCSV
+
+function setTestcase () {
+  global $testcase, $build, $level, $DUT, $test;
+
+  if (empty ($testcase)) {
+    if (empty ($test)) {
+      $test = "*";
+    } // if
+
+    if ($build == "*" and
+       $level == "*" and
+       $DUT   == "*" and
+       $test  == "*") {
+      unset ($testcase);
+      return "All Tests";
+    } else {
+      $testcase  = "${build}_${level}_${DUT}_${test}";
+    } // if
+  } // if
+
+  return $testcase;
+} // setTestcase
+
+function displayReport ($testcase, $message = "") {
+  print createHeader ();
+  print createPage   ($testcase, false, $message);
+
+  copyright ();
+} // displayReport
+
+function mailTestHistoryReport ($testcase, $pnbr, $username) {
+  global $historyFor;
+
+  if (isset ($testcase)) {
+    $subject   = "Test History for $historyFor";
+    $filename  = "Test History." . $testcase . ".csv";
+  } else {
+    $subject   = "Test History for All Tests";
+    $filename  = "Test History.All Tests.csv";
+  } // if
+
+  // Protect $filename from wildcards
+  $filename = preg_replace ("/\*/", "%", $filename);
+
+  $body                = createPage ($testcase, true);
+  $attachment  = exportCSV (getData ($testcase, true), $subject);
+
+  return mailReport ($pnbr, $username, $subject, $body, $filename, $attachment);
+} // mailTestHistoryReport
+
+openDB ();
+
+$historyFor = setTestcase ();
+
+switch ($action) {
+  case "Export":
+    exportTestHistoryCSV ($testcase);
+    break;
+
+   case "Mail":
+     list ($pnbr, $username) = explode (":", $user);
+     displayReport ($testcase, mailTestHistoryReport ($testcase, $pnbr, $username));
+     break;
+
+  default:
+    displayReport ($testcase);
+    break;
+} // switch  
+?>
+</body>
+</html>
diff --git a/rantest/web/Rantest/TestStats.php b/rantest/web/Rantest/TestStats.php
new file mode 100644 (file)
index 0000000..5cb2e06
--- /dev/null
@@ -0,0 +1,290 @@
+<?php
+////////////////////////////////////////////////////////////////////////////////
+//
+// File:       TestStats.php
+// Revision:   1.2
+// Description:        Produce a report or chart of the test statistics
+// Author:     Andrew@ClearSCM.com
+// Created:    Mon Apr 28 15:20:06 MST 2008
+// Modified:   
+// Language:   PHP
+//
+// (c) Copyright 2008, General Dynamics, all rights reserved.
+//
+// All rights reserved except as subject to DFARS 252.227-7014 of contract
+// number CP02H8901N issued under prime contract N00039-04-C-2009.
+//
+// Warning: This document contains technical data whose export is restricted
+// by the Arms Export Control Act (Title 22, U.S.C., Sec 2751, et seq.) or the
+// Export Administration Act of 1979, as amended, Title, 50, U.S.C., App. 2401
+// et seq. Violations of these export laws are subject to severe criminal
+// penalties. Disseminate in accordance with provisions of DoD Directive
+// 5230.25.
+//
+////////////////////////////////////////////////////////////////////////////////
+$script = basename ($_SERVER["PHP_SELF"]);
+
+include_once "$_SERVER[DOCUMENT_ROOT]/php/Utils.php";
+include_once "$_SERVER[DOCUMENT_ROOT]/php/RantestDB.php";
+
+$start         = $_REQUEST["start"];
+$end           = $_REQUEST["end"];
+$user          = $_REQUEST["user"];
+
+$action                = (empty ($_REQUEST["action"]))    ? "Report"     : $_REQUEST["action"];
+$type          = (empty ($_REQUEST["type"]))      ? "All"        : $_REQUEST["type"];
+$sortBy                = (empty ($_REQUEST["sortBy"]))    ? "Date"       : $_REQUEST["sortBy"];
+$direction     = (empty ($_REQUEST["direction"])) ? "descending" : $_REQUEST["direction"];
+
+function getData ($start, $end) {
+  global $sortBy;
+
+  $data = getStatus ($start, $end);
+
+  // Sort data
+  if ($sortBy == "Passed") {
+    uasort ($data, "sortSuccess");
+  } elseif ($sortBy == "Failed") {
+    uasort ($data, "sortFailure");
+  } elseif ($sortBy == "Total") {
+    uasort ($data, "sortTotal");
+  } else {
+    uasort ($data, "sortDate");
+  } // if
+
+  return $data;
+} // getData
+
+function exportStats ($start, $end) {
+  $title       = "Test Statistics from $start to $end";
+  $filename    = "Test Statistics." . $start . "." . $end . ".csv";
+
+  header ("Content-Type: application/octect-stream");
+  header ("Content-Disposition: attachment; filename=\"$filename\"");
+
+  print exportCSV (getData ($start, $end), $title);
+
+  exit;
+} // exportStats
+
+function createHeader () {
+  global $start, $end;
+
+  $header = <<<END
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
+   "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+  <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+  <link rel="stylesheet" type="text/css" media="screen" href="/css/Testing.css">
+  <link rel="stylesheet" type="text/css" media="screen" href="/css/Tables.css">
+  <title>Test Statistics from $start to $end></title>
+</head>
+<body>
+END;
+
+  $header .= banner ();
+  $header .= <<<END
+<h1 align="center">Test Statistics from $start to $end</h1>
+END;
+
+  return $header;
+} // createHeader
+
+function CreatePage ($start, $end, $forEmail = false, $message = "") {
+  global $sortBy, $direction, $script;
+
+  $data = getData ($start, $end);
+
+  $row_nbr = 0;
+
+  if (!$forEmail) {
+    // Flip direction
+    $direction = ($direction == "ascending") ? "descending" : "ascending";
+    $urlParms  = "$script?start=$start&end=$end&action=$action&direction=$direction&sortBy";
+
+    if ($sortBy == "Passed") {
+      $passedDirection = ($direction == "ascending") 
+       ? "<img src=/images/down.png align=absmiddle border=0>"
+       : "<img src=/images/up.png border=0>";
+    } elseif ($sortBy == "Failed") {
+      $failedDirection = ($direction == "ascending") 
+       ? "<img src=/images/down.png border=0>"
+       : "<img src=/images/up.png align=absmiddle border=0>";
+    } elseif ($sortBy == "Total") {
+      $totalDirection = ($direction == "ascending") 
+       ? "<img src=/images/down.png border=0>"
+       : "<img src=/images/up.png align=absmiddle border=0>";
+    } else {
+      $dateDirection = ($direction == "ascending") 
+       ? "<img src=/images/down.png border=0>"
+       : "<img src=/images/up.png align=absmiddle border=0>";
+    } // if
+
+    if (isset ($message)) {
+      $page .= "<div align=center>$message</div>";
+    } // if
+  } // if
+
+  $page .= <<<END
+<table align=center width=60%>
+  <thead>
+END;
+
+  if (!$forEmail) {
+    $page .= <<<END
+    <tr>
+      <th class=clear align="left" colspan="2"><a href="$script?action=Export&start=$start&end=$end"><input type="submit" value="Export to CSV"></a>&nbsp;<a href="$script?action=Graph&start=$start&end=$end"><input type="submit" value="Graph"></a></th>
+      <th class=clear align="right" colspan="2"><form action="$script?action=Mail&start=$start&end=$end" method="post">
+END;
+
+    $page .= emailUsersDropdown ();
+    $page .= <<<END
+        </select>
+        <input type="submit" value="Send"></form>
+      </th>
+    </tr>
+END;
+  } // if
+
+  $page .= <<<END
+    <tr>
+      <th class=left><a href="$urlParms=Date">Date&nbsp;$dateDirection</a></th>
+      <th><a href="$urlParms=Passed">Total Passed&nbsp;$passedDirection</a></th>
+      <th><a href="$urlParms=Failed">Total Failed&nbsp;$failedDirection</a></th>
+      <th class=right><a href="$urlParms=Total">Total Run&nbsp;$totalDirection</a></th>
+    </tr>
+  </thead>
+  <tbody>
+END;
+
+  foreach ($data as $result) {
+    $reportDate = YMD2MDY ($result["Date"]);
+    $row_color = ($row_nbr++ % 2 == 0) ? " class=other" : " class=white";
+
+    $page .= <<<END
+    <tr $row_color>
+      <td align=center><a href="rantest.php?day=$reportDate">$reportDate</a></td>
+      <td align=right>$result[Success]</td>
+      <td align=right>$result[Failure]</td>
+      <td align=right>$result[Total]</td>
+    </tr>
+END;
+  } // foreach
+
+  $page .= <<<END
+  </tbody>
+</table>
+END;
+
+  return $page;
+} // CreatePage
+
+function displayReport ($start, $end, $message = "") {
+  print createHeader   ($start, $end);
+  print createPage     ($start, $end, false, $message);
+
+  copyright ();
+} // displayReport
+
+function displayChart ($start, $end) {
+  global $testTypes, $type;
+
+  print createHeader ($start, $end);
+
+  $days = getdays();
+
+  print <<<END
+    <div align="center">
+      <form action="TestStats.php">
+      Type: <select name="type" class="inputfield">
+END;
+
+    foreach ($testTypes as $t) {
+      if ($type != $t) {
+       print "<option>$t</option>";
+      } else {
+       print "<option selected=\"selected\">$t</option>";
+      } // if
+    } // foreach
+
+  print <<<END
+    </select>
+    From:
+        <select name=start class=inputfield>
+END;
+
+  foreach ($days as $day) {
+    print "<option";
+
+    if ($day == $start) {
+      print " selected=selected";
+    } // if
+
+    print ">$day</option>\n";
+  } // foreach
+
+  print <<<END
+        </select> To: 
+        <select name=end class=inputfield>
+END;
+
+  foreach ($days as $day) {
+    print "<option";
+
+    if ($day == $end) {
+      print " selected=selected";
+    } // if
+
+    print ">$day</option>\n";
+  } // foreach
+
+  print <<<END
+        </select>
+          <input type=submit name="action" value="Graph">&nbsp;<a href="$script?action=Report&start=$start&end=$end"><input type="submit" value="Report"></a><br><br>
+      </form>
+        <img src="GraphStats.php?start=$start&end=$end&type=$type">
+    </div>
+END;
+
+  copyright ();
+} // displayChart
+
+function mailTestStatsReport ($start, $end, $pnbr, $username) {
+  $subject     = "Test Statistics from $start to $end";
+  $filename    = "TestStats.$start-$end.csv";
+  $body                = createPage ($start, $end, true);
+  $attachment  = exportCSV (getData ($start, $end), $subject);
+
+  return mailReport ($pnbr, $username, $subject, $body, $filename, $attachment);
+} // mailReport
+
+if (MDY2YMD ($start) > MDY2YMD ($end)) {
+  Error ("<b>Start Date</b> must come before <b>End Date</b>");
+  return;
+} // if
+
+openDB ();
+
+switch ($action) {
+  case "Graph":
+    displayChart ($start, $end);
+    break;
+
+  case "Export":
+    exportStats ($start, $end);
+    break;
+
+  case "Mail":
+    list ($pnbr, $username) = explode (":", $user);
+    displayReport ($start, $end, mailTestStatsReport ($start, $end, $pnbr, $username));
+    break;
+
+  default:
+    displayReport ($start, $end);
+
+    break;
+} // switch  
+?>
+</body>
+</html>
diff --git a/rantest/web/Rantest/TestcasePerVersion.php b/rantest/web/Rantest/TestcasePerVersion.php
new file mode 100644 (file)
index 0000000..762a60c
--- /dev/null
@@ -0,0 +1,254 @@
+<?php
+////////////////////////////////////////////////////////////////////////////////
+//
+// File:       TestcasePerVersion.php
+// Revision:   1.2
+// Description:        Produce a report of the testcases per version
+// Author:     Andrew@ClearSCM.com
+// Created:    Mon Apr 28 15:20:06 MST 2008
+// Modified:   
+// Language:   PHP
+//
+// (c) Copyright 2008, General Dynamics, all rights reserved.
+//
+// All rights reserved except as subject to DFARS 252.227-7014 of contract
+// number CP02H8901N issued under prime contract N00039-04-C-2009.
+//
+// Warning: This document contains technical data whose export is restricted
+// by the Arms Export Control Act (Title 22, U.S.C., Sec 2751, et seq.) or the
+// Export Administration Act of 1979, as amended, Title, 50, U.S.C., App. 2401
+// et seq. Violations of these export laws are subject to severe criminal
+// penalties. Disseminate in accordance with provisions of DoD Directive
+// 5230.25.
+//
+////////////////////////////////////////////////////////////////////////////////
+$script = basename ($_SERVER["PHP_SELF"]);
+
+include_once "$_SERVER[DOCUMENT_ROOT]/php/Utils.php";
+include_once "$_SERVER[DOCUMENT_ROOT]/php/RantestDB.php";
+
+$version       = $_REQUEST["version"];
+$user          = $_REQUEST["user"];
+
+$action                = (empty ($_REQUEST["action"]))    ? "Report"     : $_REQUEST["action"];
+$type          = (empty ($_REQUEST["type"]))      ? "All"        : $_REQUEST["type"];
+$sortBy                = (empty ($_REQUEST["sortBy"]))    ? "Start"       : $_REQUEST["sortBy"];
+$direction     = (empty ($_REQUEST["direction"])) ? "descending" : $_REQUEST["direction"];
+
+function getData ($version) {
+  global $sortBy;
+
+  $data = getTestcaseVersions ($version);
+
+  // Sort data
+  if ($sortBy == "Testcase") {
+    uasort ($data, "sortTestcase");
+  } elseif ($sortBy == "Unit") {
+    uasort ($data, "sortUnit");
+  } elseif ($sortBy == "Type") {
+    uasort ($data, "sortType");
+  } elseif ($sortBy == "Status") {
+    uasort ($data, "sortStatus");
+  } elseif ($sortBy == "Duration") {
+    uasort ($data, "sortDuration");
+  } else {
+    uasort ($data, "sortStart");
+  } // if    
+
+  return $data;
+} // getData
+
+function createHeader () {
+  global $versionFor;
+
+  $header = <<<END
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
+   "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+  <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+  <link rel="stylesheet" type="text/css" media="screen" href="/css/Testing.css">
+  <link rel="stylesheet" type="text/css" media="screen" href="/css/Tables.css">
+  <title>Testcases for $versionFor</title>
+</head>
+
+<body>
+END;
+
+  $header .= banner ();
+  $header .= <<<END
+<h1 align="center">Testcases for $versionFor</h1>
+END;
+
+  return $header;
+} // createHeader
+
+function createPage ($version, $forEmail = false, $message = "") {
+  global $webdir, $direction, $sortBy, $script;
+
+  $data = getData ($version);
+
+  // Flip direction
+  $direction = ($direction == "ascending") ? "descending" : "ascending";
+
+  $urlParms  = "$script?version=$version&action=$action&direction=$direction&sortBy";
+
+  if (!$forEmail) {
+    if ($sortBy == "Testcase") {
+      $testcaseDirection = ($direction == "ascending") 
+       ? "<img src=/images/down.png border=0>"
+       : "<img src=/images/up.png border=0>";
+    } elseif ($sortBy == "Unit") {
+      $unitDirection = ($direction == "ascending") 
+       ? "<img src=/images/down.png border=0>"
+       : "<img src=/images/up.png border=0>";
+    } elseif ($sortBy == "Type") {
+      $typeDirection = ($direction == "ascending") 
+       ? "<img src=/images/down.png border=0>"
+       : "<img src=/images/up.png border=0>";
+    } elseif ($sortBy == "Status") {
+      $statusDirection = ($direction == "ascending") 
+       ? "<img src=/images/down.png border=0>"
+       : "<img src=/images/up.png border=0>";
+    } elseif ($sortBy == "Duration") {
+      $durationDirection = ($direction == "ascending") 
+       ? "<img src=/images/down.png border=0>"
+       : "<img src=/images/up.png border=0>";
+    } else {
+      $startDirection = ($direction == "ascending") 
+       ? "<img src=/images/down.png border=0>"
+       : "<img src=/images/up.png border=0>";
+    } // if
+      
+    if (isset ($message)) {
+      $page .= "<div align=\"center\">$message</div>";
+    } // if
+  } // if
+
+  $page .= <<<END
+<table align="center" width="90%">
+  <thead>
+END;
+
+  if (!$forEmail) {
+    $page .= <<<END
+    <tr>
+      <th class=clear align="left" colspan="2"><a href="$script?action=Export&version=$version"><input type="submit" value="Export to CSV"></a></th>
+      <th class=clear align="right" colspan="8"><form action="$script?action=Mail&version=$version" method="post">
+END;
+
+    $page .= emailUsersDropdown ();
+    $page .= <<<END
+        <input type="submit" value="Send"></form>
+      </th>
+    </tr>
+END;
+  } // if
+
+  $page .= <<<END
+    <tr>
+      <th class=left>#</th>
+      <th><a href="$urlParms=Testcase">Testcase&nbsp;$testcaseDirection</a></th>
+      <th><a href="$urlParms=Start">Start Date/Time&nbsp;$startDirection</a></th>
+      <th><a href="$urlParms=Unit">Unit&nbsp;$unitDirection</a></th>
+      <th><a href="$urlParms=Type">Type&nbsp;$typeDirection</a></th>
+      <th><a href="$urlParms=Status">Status&nbsp;$statusDirection</a></th>
+      <th class=right><a href="$urlParms=Duration">Duration&nbsp;$durationDirection</a></th>
+    </tr>
+  </thead>
+  <tbody>
+END;
+
+  foreach ($data as $line) {
+    $row_nbr++;
+    $row_color         = setRowColor ($line["Status"]);
+    $line["Status"]    = colorResult ($line["Status"]);
+    $duration          = FormatDuration ($line["Duration"]);
+
+    $page .= <<<END
+    <tr $row_color>
+      <td align="center">$row_nbr</td>
+      <td><a href="rantest.php?testName=$line[Testcase]&runid=$line[_runid]&start=$line[Start]">$line[Testcase]</a></td>
+      <td align="center">$line[Start]</td>
+      <td align="center">$line[Unit]</td>
+      <td align="center">$line[Type]</td>
+      <td align="center">$line[Status]</td>
+      <td align="right">$duration</td>
+    </tr>
+END;
+  } // foreach
+
+  $page .= <<<END
+  </tbody>
+</table>
+END;
+
+  return $page;
+} // createPage
+
+function exportTestVersionsCSV ($version) {
+  if (isset ($version)) {
+    $title     = "Testcases for $version";
+    $filename  = "Testcases." . $version . ".csv";
+  } else {
+    $title     = "Testcases for All Versions";
+    $filename  = "Testcases for All Versionss.csv";
+  } // if
+
+  header ("Content-Type: application/octect-stream");
+  header ("Content-Disposition: attachment; filename=\"$filename\"");
+
+  print exportCSV (getData ($version), $title);
+
+  exit;
+} // exportTestVersionsCSV
+
+function setVersion () {
+  global $version;
+
+  return (isset ($version)) ? $version : "All Versions";
+} // setVersion
+
+function displayReport ($version, $message = "") {
+  print createHeader   ();
+  print createPage     ($version, false, $message);
+
+  copyright ();
+} // displayReport
+
+function mailTestVersionsReport ($version, $pnbr, $username) {
+  if (isset ($version)) {
+    $subject   = "Testcases for $version";
+    $filename  = "Testcases." . $version . ".csv";
+  } else {
+    $subject   = "Test Versions for All Tests";
+    $filename  = "Test Versions for All Tests.csv";
+  } // if
+
+  $body                = createPage ($version, true);
+  $attachment  = exportCSV (getData ($version, true), $subject);
+
+  return mailReport ($pnbr, $username, $subject, $body, $filename, $attachment);
+} // mailTestVersionsReport
+
+openDB ();
+
+$versionFor = setVersion ();
+
+switch ($action) {
+  case "Export":
+    exportTestVersionsCSV ($version);
+    break;
+
+  case "Mail":
+    list ($pnbr, $username) = explode (":", $user);
+    displayReport ($version, mailTestVersionsReport ($version, $pnbr, $username));
+    break;
+
+  default:
+    displayReport ($version);
+    break;
+} // switch  
+?>
+</body>
+</html>
diff --git a/rantest/web/Rantest/VersionPerTestcase.php b/rantest/web/Rantest/VersionPerTestcase.php
new file mode 100644 (file)
index 0000000..e54ecb9
--- /dev/null
@@ -0,0 +1,308 @@
+<?php
+////////////////////////////////////////////////////////////////////////////////
+//
+// File:       VersionPerTestcase.php
+// Revision:   1.2
+// Description:        Produce a report of versions per testcase
+// Author:     Andrew@ClearSCM.com
+// Created:    Mon Apr 28 15:20:06 MST 2008
+// Modified:   
+// Language:   PHP
+//
+// (c) Copyright 2008, General Dynamics, all rights reserved.
+//
+// All rights reserved except as subject to DFARS 252.227-7014 of contract
+// number CP02H8901N issued under prime contract N00039-04-C-2009.
+//
+// Warning: This document contains technical data whose export is restricted
+// by the Arms Export Control Act (Title 22, U.S.C., Sec 2751, et seq.) or the
+// Export Administration Act of 1979, as amended, Title, 50, U.S.C., App. 2401
+// et seq. Violations of these export laws are subject to severe criminal
+// penalties. Disseminate in accordance with provisions of DoD Directive
+// 5230.25.
+//
+////////////////////////////////////////////////////////////////////////////////
+$script = basename ($_SERVER["PHP_SELF"]);
+
+include_once "$_SERVER[DOCUMENT_ROOT]/php/Utils.php";
+include_once "$_SERVER[DOCUMENT_ROOT]/php/RantestDB.php";
+
+$testcase      = $_REQUEST["testcase"];
+$user          = $_REQUEST["user"];
+
+$build         = (empty ($_REQUEST["build"]))     ? "*"         : $_REQUEST["build"];
+$level         = (empty ($_REQUEST["level"]))     ? "*"         : $_REQUEST["level"];
+$DUT           = (empty ($_REQUEST["DUT"]))       ? "*"         : $_REQUEST["DUT"];
+$test          = (empty ($_REQUEST["test"]))      ? "*"         : $_REQUEST["test"];
+
+$action                = (empty ($_REQUEST["action"]))    ? "Report"     : $_REQUEST["action"];
+$type          = (empty ($_REQUEST["type"]))      ? "All"        : $_REQUEST["type"];
+$sortBy                = (empty ($_REQUEST["sortBy"]))    ? "Date"       : $_REQUEST["sortBy"];
+$direction     = (empty ($_REQUEST["direction"])) ? "descending" : $_REQUEST["direction"];
+
+$historyFor    = setTestcase ($testcase);
+
+// Sorting functions
+function sortVersion ($a, $b) {
+  global $direction;
+
+  return cmpStr ($a, $b, "Version", $direction);
+} // sortUnitVersion
+
+function getData ($testcase) {
+  global $sortBy;
+
+  $data = getTestVersions ($testcase);
+
+  // Sort data
+  if ($sortBy == "Testcase") {
+    uasort ($data, "sortTestcase");
+  } elseif ($sortBy == "Unit") {
+    uasort ($data, "sortUnit");
+  } elseif ($sortBy == "Type") {
+    uasort ($data, "sortType");
+  } elseif ($sortBy == "Status") {
+    uasort ($data, "sortStatus");
+  } elseif ($sortBy == "Duration") {
+    uasort ($data, "sortDuration");
+  } elseif ($sortBy == "Version") {
+    uasort ($data, "sortVersion");
+  } else {
+    uasort ($data, "sortDate");
+  } // if    
+
+  return $data;
+} // getData
+
+function createHeader () {
+  global $historyFor;
+
+  $header = <<<END
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
+   "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+  <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+  <link rel="stylesheet" type="text/css" media="screen" href="/css/Testing.css">
+  <link rel="stylesheet" type="text/css" media="screen" href="/css/Tables.css">
+  <title>Test Versions for $historyFor</title>
+</head>
+
+<body>
+END;
+
+  $header .= banner ();
+  $header .= <<<END
+<h1 align="center">Test Versions for $historyFor</h1>
+END;
+
+  return $header;
+} // createHeader
+
+function createPage ($testcase, $forEmail = false, $message = "") {
+  global $webdir, $direction, $sortBy, $script;
+  global $build, $level, $DUT, $test;
+
+  $data = getData ($testcase);
+
+  // Flip direction
+  $direction = ($direction == "ascending") ? "descending" : "ascending";
+
+  if (isset ($testcase)) {
+    $urlParms  = "$script?testcase=$testcase&action=$action&direction=$direction&sortBy";
+  } else {
+    $urlParms  = "$script?build=$build&level=$level&DUT=$DUT&test=$test&action=$action&direction=$direction&sortBy";
+  } // if
+
+  if (!$forEmail) {
+    if ($sortBy == "Testcase") {
+      $testcaseDirection = ($direction == "ascending") 
+       ? "<img src=/images/down.png border=0>"
+       : "<img src=/images/up.png border=0>";
+    } elseif ($sortBy == "Unit") {
+      $unitDirection = ($direction == "ascending") 
+       ? "<img src=/images/down.png border=0>"
+       : "<img src=/images/up.png border=0>";
+    } elseif ($sortBy == "Type") {
+      $typeDirection = ($direction == "ascending") 
+       ? "<img src=/images/down.png border=0>"
+       : "<img src=/images/up.png border=0>";
+    } elseif ($sortBy == "Status") {
+      $statusDirection = ($direction == "ascending") 
+       ? "<img src=/images/down.png border=0>"
+       : "<img src=/images/up.png border=0>";
+    } elseif ($sortBy == "Duration") {
+      $durationDirection = ($direction == "ascending") 
+       ? "<img src=/images/down.png border=0>"
+       : "<img src=/images/up.png border=0>";
+    } elseif ($sortBy == "Version") {
+      $versionDirection = ($direction == "ascending") 
+       ? "<img src=/images/down.png border=0>"
+       : "<img src=/images/up.png border=0>";
+    } else {
+      $dateDirection = ($direction == "ascending") 
+       ? "<img src=/images/down.png border=0>"
+       : "<img src=/images/up.png border=0>";
+    } // if
+      
+    if (isset ($message)) {
+      $page .= "<div align=\"center\">$message</div>";
+    } // if
+  } // if
+
+  $page .= <<<END
+<table align="center" width="90%">
+  <thead>
+END;
+
+  if (!$forEmail) {
+    $page .= <<<END
+    <tr>
+      <th class=clear align="left" colspan="2"><a href="$script?action=Export&testcase=$testcase"><input type="submit" value="Export to CSV"></a></th>
+      <th class=clear align="right" colspan="6"><form action="$script?action=Mail&testcase=$testcase" method="post">
+END;
+
+    $page .= emailUsersDropdown ();
+    $page .= <<<END
+        </select>
+        <input type="submit" value="Send"></form>
+      </th>
+    </tr>
+END;
+  } // if
+
+  $page .= <<<END
+    <tr>
+      <th class=left>#</th>
+      <th><a href="$urlParms=Testcase">Testcase&nbsp;$testcaseDirection</a></th>
+      <th><a href="$urlParms=Date">Start Date/Time&nbsp;$dateDirection</a></th>
+      <th><a href="$urlParms=Unit">Unit&nbsp;$unitDirection</a></th>
+      <th><a href="$urlParms=Type">Type&nbsp;$typeDirection</a></th>
+      <th><a href="$urlParms=Status">Status&nbsp;$statusDirection</a></th>
+      <th><a href="$urlParms=Duration">Duration&nbsp;$durationDirection</a></th>
+      <th class=right><a href="$urlParms=Version">Version&nbsp;$versionDirection</a></th>
+    </tr>
+  </thead>
+  <tbody>
+END;
+
+  foreach ($data as $line) {
+    $row_nbr++;
+    $row_color         = setRowColor ($line["Status"]);
+    $line["Status"]    = colorResult ($line["Status"]);
+    $duration          = FormatDuration ($line["Duration"]);
+
+    $page .= <<<END
+    <tr $row_color>
+      <td align="center">$row_nbr</td>
+      <td><a href="rantest.php?testName=$line[Testcase]&runid=$line[_runid]&date=$reportDate">$line[Testcase]</a></td>
+      <td align="center">$line[Start]</td>
+      <td align="center">$line[Unit]</td>
+      <td align="center">$line[Type]</td>
+      <td align="center">$line[Status]</td>
+      <td align="right">$duration</td>
+      <td align="center">$line[Version]</td>
+    </tr>
+END;
+  } // foreach
+
+  $page .= <<<END
+  </tbody>
+</table>
+END;
+
+  return $page;
+} // createPage
+
+function exportTestVersionsCSV ($testcase) {
+  global $historyFor;
+
+  if (isset ($testcase)) {
+    $title     = "Test Versions for $historyFor";
+    $filename  = "Test Versions." . $testcase . ".csv";
+  } else {
+    $title     = "Test Versions for All Tests";
+    $filename  = "Test Versions for All Tests.csv";
+  } // if
+
+  // Protect $filename from wildcards
+  $filename = preg_replace ("/\*/", "%", $filename);
+
+  header ("Content-Type: application/octect-stream");
+  header ("Content-Disposition: attachment; filename=\"$filename\"");
+
+  print exportCSV (getData ($testcase), $title);
+
+  exit;
+} // exportTestHistoryCSV
+
+function setTestcase () {
+  global $testcase, $build, $level, $DUT, $test;
+
+  if (empty ($testcase)) {
+    if (empty ($test)) {
+      $test = "*";
+    } // if
+
+    if ($build == "*" and
+       $level == "*" and
+       $DUT   == "*" and
+       $test  == "*") {
+      unset ($testcase);
+      return "All Tests";
+    } else {
+      $testcase  = "${build}_${level}_${DUT}_${test}";
+    } // if
+  } // if
+
+  return $testcase;
+} // setTestcase
+
+function displayReport ($testcase, $message = "") {
+  print createHeader   ();
+  print createPage     ($testcase, false, $message);
+
+  copyright ();
+} // displayReport
+
+function mailTestVersionsReport ($testcase, $pnbr, $username) {
+  global $historyFor;
+
+  if (isset ($testcase)) {
+    $subject   = "Test Versions for $historyFor";
+    $filename  = "Test Versions." . $testcase . ".csv";
+  } else {
+    $subject   = "Test Versions for All Tests";
+    $filename  = "Test Versions for All Tests.csv";
+  } // if
+
+  // Protect $filename from wildcards
+  $filename = preg_replace ("/\*/", "%", $filename);
+
+  $body                = createPage ($testcase, true);
+  $attachment  = exportCSV (getData ($testcase, true), $subject);
+
+  return mailReport ($pnbr, $username, $subject, $body, $filename, $attachment);
+} // mailTestVersionsReport
+
+openDB ();
+
+$historyFor = setTestcase ();
+
+switch ($action) {
+  case "Export":
+    exportTestVersionsCSV ($testcase);
+    break;
+
+  case "Mail":
+    list ($pnbr, $username) = explode (":", $user);
+    displayReport ($testcase, mailTestVersionsReport ($testcase, $pnbr, $username));
+    break;
+
+  default:
+    displayReport ($testcase);
+    break;
+} // switch  
+?>
+</body>
+</html>
diff --git a/rantest/web/Rantest/index.php b/rantest/web/Rantest/index.php
new file mode 100644 (file)
index 0000000..03a5244
--- /dev/null
@@ -0,0 +1,296 @@
+<?php
+////////////////////////////////////////////////////////////////////////////////
+//
+// File:       index.php
+// Revision:   1.2
+// Description:        Main index page for Rantest
+// Author:     Andrew@ClearSCM.com
+// Created:    Mon Apr 28 15:20:06 MST 2008
+// Modified:   
+// Language:   PHP
+//
+// (c) Copyright 2008, General Dynamics, all rights reserved.
+//
+// All rights reserved except as subject to DFARS 252.227-7014 of contract
+// number CP02H8901N issued under prime contract N00039-04-C-2009.
+//
+// Warning: This document contains technical data whose export is restricted
+// by the Arms Export Control Act (Title 22, U.S.C., Sec 2751, et seq.) or the
+// Export Administration Act of 1979, as amended, Title, 50, U.S.C., App. 2401
+// et seq. Violations of these export laws are subject to severe criminal
+// penalties. Disseminate in accordance with provisions of DoD Directive
+// 5230.25.
+//
+////////////////////////////////////////////////////////////////////////////////
+$script = basename ($_SERVER["PHP_SELF"]);
+include_once "$_SERVER[DOCUMENT_ROOT]/php/Utils.php";
+include_once "$_SERVER[DOCUMENT_ROOT]/php/RantestDB.php";
+OpenDB();
+
+$testNames             = getTestNames();
+$days                  = getdays();
+$ran_versions          = getVersions ("ran_version");
+$latestDate            = YMD2MDY (getLatestDate ());
+$earliestDate          = YMD2MDY (getEarliestDate ());
+?>
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
+   "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+  <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+  <link rel="stylesheet" type="text/css" media="screen" href="/css/Testing.css">
+  <link rel="stylesheet" type="text/css" media="screen" href="/css/Tables.nohover.css">
+  <title>RANTEST: RAN Tool for Execution of System Tests</title>
+</head>
+
+<body>
+
+<?php
+  print banner ();
+?>
+
+  <h1 align=center><font class="standout">RAN T</font>ool for <font class="standout">E</font>xecution of <font class="standout">S</font>ystem <font class="standout">T</font>ests</h1>
+
+  <table width="95%" align="center">
+    <thead>
+      <tr>
+        <th colspan=2 class="top"><big>Execution Reports</big><br>
+<?php
+print "Status for $latestDate: ";
+print stats();
+?>
+</th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td class="shaded" width=50%>
+         <b>Test History</b><br>
+         <small>How often a test case has run, with pass/fail status<br><br>
+          <form action="TestHistory.php">
+         <table class="shaded" align="center">
+           <thead>
+              <tr>
+                <th class="left">Build</th>
+                <th>Level</th>
+                <th>DUT</th>
+                <th class="right">Test</th>
+              </tr>
+            </thead>
+            <tbody>
+             <tr class="white">
+                <td>
+                  <select name=build class=inputfield>
+                   <option selected=selected>*</option>
+                   <option>b1</option>
+                   <option>b2</option>
+                   <option>b3</option>
+                  </select>
+                </td>
+                <td>
+                  <select name=level class=inputfield>
+                   <option selected=selected>*</option>
+                   <option>l3</option>
+                   <option>l4</option>
+                   <option>fqt</option>
+                  </select>
+                </td>
+                <td>
+                 <select name=DUT class=inputfield>
+                   <option selected=selected>*</option>
+                   <option>ran</option>
+                   <option>rnc</option>
+                   <option>rbs</option>
+                   <option>rcg</option>
+                  </select>
+                </td>
+                <td><input type=text name=test size=10 class=inputfield value="*"></input></td>
+              </tr>
+              <tr class="white">
+                <td colspan=4 align=center>
+                  <input type=submit value="Report">
+                </td>
+              </tr>
+            </tbody>
+          </table>
+          </form>
+        </td>
+        <td class="shaded">
+         <b>Version per Testcase</b><br>
+          <small>For a given test case, what was the test environment (software version of each of the test equipment and RBS/RNC)</small><br><br>
+          <form action="VersionPerTestcase.php">
+         <table class="shaded" align="center">
+           <thead>
+              <tr>
+                <th class=left>Build</th>
+                <th>Level</th>
+                <th>DUT</th>
+                <th class=right>Test</th>
+              </tr>
+            </thead>
+            <tbody>
+             <tr class="white">
+                <td>
+                  <select name=build class=inputfield>
+                   <option selected=selected>*</option>
+                   <option>b1</option>
+                   <option>b2</option>
+                   <option>b3</option>
+                  </select>
+                </td>
+                <td>
+                  <select name=level class=inputfield>
+                   <option selected=selected>*</option>
+                   <option>l3</option>
+                   <option>l4</option>
+                   <option>fqt</option>
+                  </select>
+                </td>
+                <td>
+                 <select name=DUT class=inputfield>
+                   <option selected=selected>*</option>
+                   <option>ran</option>
+                   <option>rnc</option>
+                   <option>rbs</option>
+                   <option>rcg</option>
+                  </select>
+                </td>
+                <td><input type=text name=test size=10 class=inputfield value="*"></input></td>
+              </tr>
+              <tr class="white">
+                <td colspan=4 align=center>
+                  <input type=submit value="Report">
+                </td>
+              </tr>
+            </tbody>
+          </table>
+          </form>
+        </td>
+      </tr>
+      <tr class="white">
+        <td>
+         <b>Testcase per Version</b><br>
+         <small>What test cases have run against a given RBS/RNC/RAN software version<br><br>
+          <form action="TestcasePerVersion.php">
+            RAN Version:
+            <select name=version class=inputfield>
+<?php
+  foreach ($ran_versions as $version) {
+    print "<option>$version</option>\n";
+  } // foreach
+?>
+            </select>
+            <input type=submit value="Report">
+          </form>
+          </small>
+        </td>
+        <td width=50%>
+          <b>Test Statistics</b><br>
+          <small>Number of test cases run over a given period with pass/fail status<br><br>
+          <form action="TestStats.php">
+            From: 
+            <select name=start class=inputfield>
+<?php
+  foreach ($days as $day) {
+    print "<option";
+
+    if ($day == $earliestDate) {
+      print " selected=selected";
+    } // if
+
+    print ">$day</option>\n";
+  } // foreach
+?>
+            </select>
+            To: 
+            <select name=end class=inputfield>
+<?php
+  foreach ($days as $day) {
+    print "<option";
+
+    if ($day == $latestDate) {
+      print " selected=selected";
+    } // if
+
+    print ">$day</option>\n";
+  } // foreach
+?>          </select>
+            <input type=submit name="action" value="Report">&nbsp;
+            <input type=submit name="action" value="Graph">
+            </small>
+          </form>
+        </td>
+      </tr>
+      <tr>
+        <td class="shaded" width=50%>
+          <b>Failure Analysis</b><br>
+          <small>Show the reasons for failure<br><br>
+          <form action="FailureAnalysis.php">
+            <select name=day class=inputfield>
+<?php
+  foreach ($days as $day) {
+    print "<option";
+
+    if ($day == $latestDate) {
+      print " selected=selected";
+    } // if
+
+    print ">$day</option>\n";
+  } // foreach
+?>          </select>
+            <input type=submit name="action" value="Report">
+            </small>
+          </form>
+        </td>
+        <td class="shaded">
+          <b>Daily Test Report</b><br>
+          <form action="rantest.php">
+          <small>What test automation ran on:<br><br>
+            <select name=day class=inputfield>
+<?php
+  foreach ($days as $day) {
+    print "<option>$day</option>\n";
+  } // foreach
+?>     
+            </select>
+            <input type=submit value="Report">
+          </form>
+        </td>
+      </tr>
+      <tr class="white" align="center">
+        <td colspan="2">
+<?php
+exec ("ls /east/seast1/testlogs/nightly*log /east/seast1/testlogs/ranscrub.log", &$nightly_logs, &$status);
+
+if ($status != 0) {
+  print "Unable to do ls /east/seast1/testlogs/nightly*log (Status: $status)";
+} else {
+  $count = count ($nightly_logs);
+  $nbr = 1;
+
+  if ($count > 0) {
+    print "<h3>Nightly logs</h3>";
+    foreach ($nightly_logs as $nightly_log) {
+      preg_match ("/\/east\/seast1\/testlogs\/(.*)\.log/", $nightly_log, &$matches);
+
+      print "<a href=\"/testlogs/$matches[1].log\">$matches[1]</a>";
+
+      if (--$count > 0) {
+       if ($nbr++ % 5 == 0) {
+         print "|<br>";
+       } else {
+         print "&nbsp;|&nbsp;";
+       } // if
+      } // if
+    } // foreach
+  } // if
+} // if
+?>
+        </td>
+      </tr>
+    </tbody>
+  </table>
+
+<?php copyright();?>
+</body>
+</html>
\ No newline at end of file
diff --git a/rantest/web/Rantest/rantest.php b/rantest/web/Rantest/rantest.php
new file mode 100644 (file)
index 0000000..90c0853
--- /dev/null
@@ -0,0 +1,701 @@
+<?php
+////////////////////////////////////////////////////////////////////////////////
+//
+// File:       rantest.php
+// Revision:   1.2
+// Description:        Produce Daily Test Report, Test Suite Report and Test Steps
+//             report.
+// Author:     Andrew@ClearSCM.com
+// Created:    Mon Apr 28 15:20:06 MST 2008
+// Modified:   
+// Language:   PHP
+//
+// (c) Copyright 2008, General Dynamics, all rights reserved.
+//
+// All rights reserved except as subject to DFARS 252.227-7014 of contract
+// number CP02H8901N issued under prime contract N00039-04-C-2009.
+//
+// Warning: This document contains technical data whose export is restricted
+// by the Arms Export Control Act (Title 22, U.S.C., Sec 2751, et seq.) or the
+// Export Administration Act of 1979, as amended, Title, 50, U.S.C., App. 2401
+// et seq. Violations of these export laws are subject to severe criminal
+// penalties. Disseminate in accordance with provisions of DoD Directive
+// 5230.25.
+//
+////////////////////////////////////////////////////////////////////////////////
+$script = basename ($_SERVER["PHP_SELF"]);
+
+include_once "$_SERVER[DOCUMENT_ROOT]/php/Utils.php";
+include_once "$_SERVER[DOCUMENT_ROOT]/php/RantestDB.php";
+
+$testName      = $_REQUEST["testName"];
+$runid         = $_REQUEST["runid"];
+$date          = $_REQUEST["date"];
+$suiteid       = $_REQUEST["suiteid"];
+$user          = $_REQUEST["user"];
+
+$type          = (empty ($_REQUEST["type"]))      ? "All"        : $_REQUEST["type"];
+$action                = (empty ($_REQUEST["action"]))    ? "Report"     : $_REQUEST["action"];
+$sortBy                = (empty ($_REQUEST["sortBy"]))    ? "Start"      : $_REQUEST["sortBy"];
+$direction     = (empty ($_REQUEST["direction"])) ? "descending" : $_REQUEST["direction"];
+
+function createTestStepsHeader ($testcase) {
+  $header = <<<END
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
+   "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+  <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+  <link rel="stylesheet" type="text/css" media="screen" href="/css/Testing.css">
+  <link rel="stylesheet" type="text/css" media="screen" href="/css/Tables.css">
+<title>Test Steps for $testcase</title>
+</head>
+
+<body>
+END;
+
+  $header .= banner ();
+  $header .= <<<END
+<h1 align="center">Test Steps for $testcase</h1>
+END;
+
+  return $header;
+} // createTestStepsHeader
+
+function createTestRunsHeader ($day) {
+  global $dateDropdown;
+
+  $header = <<<END
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
+   "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+  <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+  <link rel="stylesheet" type="text/css" media="screen" href="/css/Testing.css">
+  <link rel="stylesheet" type="text/css" media="screen" href="/css/Tables.css">
+<title>Daily Test Report for $day</title>
+<script language="javascript">
+function ChangeDay (day, script) {
+  window.location = script + "?day=" + day;
+} // ChangeDay
+function ChangeType (day, type, script) {
+  window.location = script + "?day=" + day + "&type=" + type;
+} // ChangeType
+</script>
+</head>
+END;
+
+ $header .= banner ();
+ $header .= <<<END
+<body>
+<h1 align="center">Daily Test Report for $dateDropdown</h1>
+END;
+
+  return $header;
+} // createTestRunsHeader
+
+function createSuiteRunsHeader ($id) {
+  $suite = getName ("suite", $id);
+  
+  $header = <<<END
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
+   "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+  <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+  <link rel="stylesheet" type="text/css" media="screen" href="/css/Testing.css">
+  <link rel="stylesheet" type="text/css" media="screen" href="/css/Tables.css">
+<title>Suite Report for $suite</title>
+</head>
+
+<body>
+END;
+
+  $header .= banner ();
+
+  return $header;
+} // createSuiteRunsHeader
+
+function createTestStepsPage ($testName, $forEmail = false, $message = "") {
+  global $runid, $date, $script;
+
+  $data = getTestSteps ($runid);
+
+  if (!$forEmail) {
+    if (isset ($message)) {
+      $page .= "<div align=center>$message</div>";
+    } // if
+  } // if
+
+  $page .= <<<END
+<table align=center width=90%>
+  <caption><center>
+DUT: <font class="standout">{$data[_header][DUTVersion]}</font>
+EAST: <font class="standout">{$data[_header][EASTVersion]}</font>
+TM500: <font class="standout">{$data[_header][TM500Version]}</font>
+NMS: <font class="standout">{$data[_header][NMSVersion]}</font>
+RANTEST: <font class="standout">{$data[_header][RANTESTVersion]}</font>
+Date: <font class="standout">$date</font>
+  </center></caption>
+  <thead>
+END;
+
+  if (!$forEmail) {
+    $page .= <<<END
+    <tr>
+      <th class="clear" align="left"><a href="$script?action=Export&testName=$testName&runid=$runid&date=$date"><input type="submit" value="Export to CSV"></a>&nbsp;<a href="TestHistory.php?testcase=$testName"><input type="submit" value="History"></a></th>
+      <th class=clear colspan="4" align="right"><form action="$script?action=Mail&date=$date&testName=$testName&runid=$runid" method="post">
+END;
+
+    $page .= emailUsersDropdown ();
+    $page .= <<<END
+    </select>
+    <input type="submit" value="Send"></form>
+    </th>
+    </tr>
+END;
+  } // if
+
+  $page .= <<<END
+    <tr>
+      <th class=left>Step</th>
+      <th>Status</th>
+      <th>Start</th>
+      <th>End</th>
+      <th class=right>Duration</th>
+    </tr>
+  </thead>
+  <tbody>
+END;
+
+  foreach ($data["_steps"] as $line) {
+    $steps++;
+    $row_color         = setRowColor ($line["Status"]);
+    $line["Status"]    = colorResult ($line["Status"]);
+    $total_time               += $line[Duration];
+    $startTime         = substr ($line["Start"], 11, 8);
+    $endTime           = substr ($line["End"],   11, 8);
+
+    $page .= <<<END
+      <tr $row_color>
+        <td>$line[Step]</td>
+        <td>$line[Status]</td>
+        <td align="center">$startTime</td>
+        <td align="center">$endTime</td>
+        <td align="right">
+END;
+    $page .= FormatDuration ($line[Duration]);
+    $page .= "</tr>";
+  } // foreach
+
+  $total_duration = FormatDuration ($total_time);
+
+  $logs = logs ($data["_header"]["_eastlogs"], $forEmail);
+
+  $username = getUserName ($data["_header"]["userid"]);
+
+  $page .= <<<END
+  <tfoot>
+    <tr>
+      <th align="left">Total steps run in $testName: $steps</th>
+      <th align="right" colspan="2">Run by: <a href="mailto:{$data[_header][userid]}@gdc4s.com" class="tablelink">$username</a></th>
+      <th align="center">$logs</th>
+      <th align="right">$total_duration</th>
+    </tr>
+  </tfoot>
+  </tbody>
+</table>
+END;
+
+  return $page;
+} // createTestStepsPage
+
+function createTestRunsPage ($day, $forEmail = false, $message = "") {
+  global $sortBy, $direction, $day, $type, $script, $testTypes;
+
+  if (!$forEmail) {
+    if ($sortBy == "Suite") {
+      $suiteDirection = ($direction == "ascending") 
+       ? "<img src=/images/down.png border=0>"
+        : "<img src=/images/up.png border=0>";
+    } elseif ($sortBy == "Testcase") {
+      $testcaseDirection = ($direction == "ascending") 
+        ? "<img src=/images/down.png border=0>"
+        : "<img src=/images/up.png border=0>";
+    } elseif ($sortBy == "Type") {
+      $typeDirection = ($direction == "ascending") 
+       ? "<img src=/images/down.png border=0>"
+        : "<img src=/images/up.png border=0>";
+    } elseif ($sortBy == "Unit") {
+      $unitDirection = ($direction == "ascending") 
+       ? "<img src=/images/down.png border=0>"
+        : "<img src=/images/up.png border=0>";
+    } elseif ($sortBy == "Status") {
+      $statusDirection = ($direction == "ascending") 
+       ? "<img src=/images/down.png border=0>"
+        : "<img src=/images/up.png border=0>";
+    } elseif ($sortBy == "Start") {
+      $startDirection = ($direction == "ascending") 
+       ? "<img src=/images/down.png border=0>"
+        : "<img src=/images/up.png border=0>";
+    } elseif ($sortBy == "End") {
+      $endDirection = ($direction == "ascending") 
+       ? "<img src=/images/down.png border=0>"
+        : "<img src=/images/up.png border=0>";
+    } elseif ($sortBy == "Duration") {
+      $durationDirection = ($direction == "ascending") 
+       ? "<img src=/images/down.png border=0>"
+       : "<img src=/images/up.png border=0>";
+    } // if
+
+    if (isset ($message)) {
+      $page .= "<div align=center>$message</div>";
+    } // if
+  } // if
+
+  $page .= <<<END
+<table align=center>
+  <thead>
+END;
+
+  if (!$forEmail) {
+    $page .= <<<END
+    <tr>
+      <th class="clear" align="left" colspan="5">
+<a href="$script?action=Export&day=$day"><input type="submit"
+ value="Export to CSV"></a>&nbsp;<a href="FailureAnalysis.php?day=$day">
+<input type="submit" value="Analyze Failures"></a>
+&nbsp;Type: <select name="type" class="inputfield" onChange="ChangeType('$day',this.value,'$script');">
+END;
+
+    foreach ($testTypes as $t) {
+      if ($type != $t) {
+       $page .= "<option>$t</option>";
+      } else {
+       $page .= "<option selected=\"selected\">$t</option>";
+      } // if
+    } // foreach
+
+    $page .= <<<END
+      </th>
+      <th class=clear colspan="5" align="right"><form action="$script?action=Mail&day=$day&type=$type" method="post">
+END;
+
+    $page .= emailUsersDropdown ();
+    $page .= <<<END
+    </select>
+    <input type="submit" value="Send"></form>
+    </th>
+    </tr>
+END;
+  } else {
+    $page .= "<th class=\"clear\" colspan=\"10\">Type: $type Sorted by: $sortBy ($direction)</th>";
+  } // if
+
+  $direction = ($direction == "ascending") ? "descending" : "ascending";
+  $urlParms  = "$script?day=$day&&direction=$direction&type=$type&sortBy";
+
+  $page .= <<<END
+    <tr>
+      <th class="left">#</th>
+      <th><a href="$urlParms=Suite">Suite&nbsp;$suiteDirection</a></th>
+      <th><a href="$urlParms=Testcase">Testcase&nbsp;$testcaseDirection</a></th>
+      <th><a href="$urlParms=Type">Type&nbsp;$typeDirection</a></th>
+      <th><a href="$urlParms=Unit">Unit/Version&nbsp;$unitDirection</a></th>
+      <th>Logs</th>
+      <th><a href="$urlParms=Status">Status&nbsp;$statusDirection</a></th>
+      <th><a href="$urlParms=Start">Start&nbsp;$startDirection</a></th>
+      <th><a href="$urlParms=End">End&nbsp;$endDirection</a></th>
+      <th class="right"><a href="$urlParms=Duration">Duration&nbsp;$durationDirection</</th>
+    </tr>
+  </thead>
+  <tbody>
+END;
+
+  $page .= <<<END
+END;
+
+  $data = getTestRuns (MDY2YMD ($day), $type);
+
+  $total_time = 0;
+
+  foreach ($data as $line) {
+    // WARNING: This is odd! $line["Suite"] should be empty if there
+    // was no suite associated with it (thereby suiteid=0) but for
+    // some odd reason due to the complex select statement used
+    // suiteid ends up being 1 which is associated with the suite name
+    // of "nightly".
+    if ($line["Suite"] == "nightly") {
+      $line["Suite"] = "<font color=#999999>Standalone</font>";
+    } else {
+      $line["Suite"] = "<a href=\"$me?suiteid=$line[_suiteid]\">$line[Suite]</a>";
+    } // if 
+
+    $row_color   = setRowColor ($line["Status"]);
+    $testResult  = colorResult ($line["Status"]);
+    $total_time += $line["Duration"];
+    $me                 = ($script == "index.php") ? "" : $script;
+    $logs       = logs ($line["_eastlogs"], $forEmail);
+    $tests++;
+
+    $page .= <<<END
+    <tr $row_color>
+      <td align=center>$tests</td>
+      <td>$line[Suite]</td>
+      <td><a href=$me?testName=
+END;
+    $page .= $line["Testcase"];
+    $page .= "&runid=$line[_runid]&date=$day>";
+    $page .= $line["Testcase"];
+    $page .= <<<END
+</a></td>
+      <td align="center">$line[Type]</td>
+      <td align="center">
+END;
+    $page .= $line["Unit/Version"];
+    $page .= <<<END
+</td>
+      <td align="center">$logs</td>
+      <td align="center">$testResult</td>
+      <td align="center">$line[Start]</td>
+      <td align="center">$line[End]</td>
+      <td align="right">
+END;
+
+    $page .= FormatDuration ($line["Duration"]);
+    $page .= "</td></tr>";
+  } // while
+
+  $total_duration = FormatDuration ($total_time);
+
+  $page .= <<<END
+  <tfoot>
+    <tr>
+      <th align="left" colspan="9">$tests Run&nbsp;
+END;
+  $page .= stats ($day, $type);
+  $page .= <<<END
+</th>
+      <th align="right">$total_duration</th>
+    </tr>
+  </tfoot>
+  </tbody>
+</table>
+END;
+  return $page;
+} // createTestRunsPage
+
+function createSuiteRunsPage ($suiteid, $forEmail = false, $message = "") {
+  global $sortBy, $direction, $day;
+
+  $name = getName ("suite", $suiteid);
+  $page = "<h1 align=center>Test Suite Report for $name</h1>";
+
+  if (!$forEmail) {
+    if ($sortBy == "Status") {
+      $statusDirection = ($direction == "ascending") 
+       ? "<img src=/images/down.png border=0>"
+        : "<img src=/images/up.png border=0>";
+    } elseif ($sortBy == "Start") {
+      $startDirection = ($direction == "ascending") 
+       ? "<img src=/images/down.png border=0>"
+        : "<img src=/images/up.png border=0>";
+    } elseif ($sortBy == "End") {
+      $endDirection = ($direction == "ascending") 
+       ? "<img src=/images/down.png border=0>"
+        : "<img src=/images/up.png border=0>";
+    } elseif ($sortBy == "Duration") {
+      $durationDirection = ($direction == "ascending") 
+       ? "<img src=/images/down.png border=0>"
+        : "<img src=/images/up.png border=0>";
+    } // if
+
+    if (isset ($message)) {
+      $page .= "<div align=center>$message</div>";
+    } // if
+  } // if
+
+  $page .= <<<END
+<table align=center width="75%">
+  <thead>
+END;
+
+  if (!$forEmail) {
+    $page .= <<<END
+    <tr>
+      <th class="clear" align="left" colspan="2"><a href="$script?action=Export&suiteid=$suiteid"><input type="submit" value="Export to CSV"></a>
+END;
+
+    $page .= <<<END
+      </th>
+      <th class=clear colspan="2" align="right"><form action="$script?action=Mail&suiteid=$suiteid" method="post">
+END;
+
+    $page .= emailUsersDropdown ();
+    $page .= <<<END
+    </select>
+    <input type="submit" value="Send"></form>
+    </th>
+    </tr>
+END;
+  } // if
+
+  $direction = ($direction == "ascending") ? "descending" : "ascending";
+  $urlParms  = "$script?suiteid=$suiteid&direction=$direction&&sortBy";
+
+  $data = getSuiteRuns ($suiteid);
+
+  $page .= <<<END
+    <tr>
+      <th class=left><a href="$urlParms=Status">Status&nbsp;$statusDirection</a></th>
+      <th><a href="$urlParms=Start">Start&nbsp;$startDirection</a></th>
+      <th><a href="$urlParms=End">End&nbsp;$endDirection</a></th>
+      <th class=right><a href="$urlParms=Duration">Duration&nbsp;$durationDirection</a></th>
+    </tr>
+  </thead>
+  <tbody>
+END;
+
+  foreach ($data as $line) {
+    $row_color = setRowColor ($line["Status"]);
+    $status    = colorResult ($line["Status"]);
+    $duration  = FormatDuration ($line["Duration"]);
+
+    $suiteruns++;
+    $total_time += $line["Duration"];
+
+    $page .= <<<END
+      <tr $row_color>
+        <td>$status</td>
+        <td align="center">$line[Start]</td>
+        <td align="center">$line[End]</td>
+        <td align="right">$duration</td>
+      </tr>
+END;
+  } // while
+
+  $total_duration = FormatDuration ($total_time);
+
+  $page .= <<<END
+  <tfoot>
+    <tr>
+      <th align="left" colspan="3">Total sutie runs for $name: $suiteruns</th>
+      <th align="right">$total_duration</th>
+    </tr>
+  </tfoot>
+  </tbody>
+</table>
+END;
+
+  return $page;
+} // createSuiteRunsPage
+
+function displayStepRun ($testName, $message = "") {
+  print createTestStepsHeader ($testName);
+  print createTestStepsPage ($testName, false, $message);
+
+  copyright ();
+} // displayStepRun
+
+function displayTestRuns ($day, $message = "") {
+  print createTestRunsHeader ($day);
+  print createTestRunsPage ($day, false, $message);
+
+  copyright ();
+} // displayTestRuns
+
+function displaySuiteRun ($suiteid, $message = "") {
+  print createSuiteRunsHeader ($suiteid);
+  print createSuiteRunsPage ($suiteid, false, $message);
+
+  copyright ();
+} // displaySuiteRun
+
+// The $data structure for test steps is unique so handle exportion
+// here.
+function exportStepRunCSV ($data, $title) {
+  global $na;
+
+  $csv .= "$title\n";
+
+  // Put out header information
+  $csv .= "DUT, EAST, TM500, NMS, RANTEST, USER, DATE, LOGS\n";
+
+  $versions = array (
+    "DUTVersion",
+    "EASTVersion",
+    "TM500Version",
+    "NMSVersion",
+    "RANTESTVersion"
+  );
+
+  foreach ($versions as $version) {
+    if ($data["_header"][$version] == $na) {
+      $csv .= "N/A,";
+    } else {
+      $csv .= $data["_header"][$version] . ",";
+    } // if
+  } // foreach
+
+  $csv .= $data["_header"]["userid"]                   . ",";
+  $csv .= YMD2MDY ($data["_header"]["Date"])           . ",";
+  $csv .= logs ($data["_header"]["_eastlogs"], true)   . "\n\n";
+
+  // Create header line
+  $firstTime = true;
+
+  foreach ($data["_steps"][0] as $key => $value) {
+    // Skip "hidden" fields - fields beginning with "_"
+    if (preg_match ("/^_/", $key) == 1) {
+      continue;
+    } // if
+
+    if (!$firstTime) {
+      $csv .= ",";
+    } else {
+      $firstTime = false;
+    } // if
+
+    $csv .= "\"$key\"";
+  } // foreach
+
+  $csv .= "\n";
+
+  foreach ($data["_steps"] as $entry) {
+    $firstTime = true;
+
+    foreach ($entry as $key => $value) {
+      // Skip "hidden" fields - fields beginning with "_"
+      if (preg_match ("/^_/", $key) == 1) {
+       continue;
+      } // if
+
+      if (!$firstTime) {
+       $csv .= ",";
+      } else {
+       $firstTime = false;
+      } // if
+
+      $csv .= "\"$value\"";
+    } // foreach
+
+    $csv .= "\n";
+  } // foreach
+
+  return $csv;
+} // exportStepRunCSV
+  
+function exportStepRun ($testcase, $runid, $date) {
+  $timestamp   = getTestRunTimestamp ($runid);
+  $filename = "Step Report." . $timestamp . ".csv";
+
+  header ("Content-Type: application/octect-stream");
+  header ("Content-Disposition: attachment; filename=\"$filename\"");
+
+  print exportStepRunCSV (getTestSteps ($runid), "Step Report for $testcase");
+
+  exit;
+} // exportStepRun
+
+function exportTestRuns ($day) {
+  global $type;
+
+  $filename = "Daily Test Report." . $day . ".csv";
+
+  header ("Content-Type: application/octect-stream");
+  header ("Content-Disposition: attachment; filename=\"$filename\"");
+
+  print exportCSV (getTestRuns (MDY2YMD ($day), $type, true), "Daily Test Report for $day");
+
+  exit;
+} // exportTestRuns
+
+function exportSuiteRun ($suiteid) {
+  $suite       = getName ("suite", $suiteid);
+  $filename    = "Suite Report." . $suite . ".csv";
+
+  header ("Content-Type: application/octect-stream");
+  header ("Content-Disposition: attachment; filename=\"$filename\"");
+
+  print exportCSV (getSuiteRuns ($suiteid), "Suite Report for $suite");
+
+  exit;
+} // exportSuiteRun
+
+function mailStepRunReport ($testName, $pnbr, $username) {
+  global $runid;
+
+  $subject     = "Step Report for $testName";
+  $body                = createTestStepsPage ($testName, true);
+  $timestamp   = getTestRunTimestamp ($runid);
+  $filename    = "Step Report." . $timestamp . ".csv";
+  $attachment  = exportStepRunCSV (getTestSteps ($runid), $subject);
+
+  return mailReport ($pnbr, $username, $subject, $body, $filename, $attachment);
+} // mailStepReport
+
+function mailTestRunsReport ($day, $pnbr, $username) {
+  global $type;
+
+  $subject     = "Daily Test Report for $day";
+  $body                = createTestRunsPage ($day, true);
+  $filename    = "TestRuns.$day.csv";
+  $attachment  = exportCSV (getTestRuns (MDY2YMD ($day), $type, true), $subject);
+
+  return mailReport ($pnbr, $username, $subject, $body, $filename, $attachment);
+} // mailTestRunsReport
+
+function mailSuiteRunReport ($suiteid, $pnbr, $username) {
+  $subject     = "Suite Report for $suitid";
+  $body                = createSuiteRunsPage ($suiteid, true);
+  $filename    = "Suite Runs.$suiteid.csv";
+
+  $attachment  = exportCSV (getSuiteRuns ($suiteid, true), $subject);
+
+  return mailReport ($pnbr, $username, $subject, $body, $filename, $attachment);
+} // mailSuiteRunReport
+
+openDB ();
+
+$dateDropdown = reportDateDropdown ();
+
+switch ($action) {
+  case "Export":
+    if ($suiteid != 0) {
+      exportSuiteRun ($suiteid);
+    } elseif (isset ($testName)) {
+      exportStepRun ($testName, $runid, $date);
+    } else {
+      exportTestRuns ($day);
+    } // if
+
+    break;
+
+  case "Mail":
+    list ($pnbr, $username) = explode (":", $user);
+
+    if (isset ($suiteid)) {
+      $message = mailSuiteRunReport ($suiteid, $pnbr, $username);
+      displaySuiteRun ($suiteid, $message);
+    } elseif (isset ($testName)) {
+      $message = mailStepRunReport ($testName, $pnbr, $username);
+      displayStepRun ($testName, $message);
+    } else {
+      $message = mailTestRunsReport ($day, $pnbr, $username);
+
+      displayTestRuns ($day, $message);
+    } // if
+
+    break;
+
+  default:
+    if (isset ($suiteid)) {
+      displaySuiteRun ($suiteid);
+    } elseif (isset ($testName)) {
+      displayStepRun ($testName);
+    } else {
+      displayTestRuns ($day);
+    } // if
+
+    break;
+} // switch  
diff --git a/rantest/web/index.php b/rantest/web/index.php
new file mode 100644 (file)
index 0000000..44a99e1
--- /dev/null
@@ -0,0 +1 @@
+<meta http-equiv="refresh" content="0;url=/Rantest">
diff --git a/rantest/web/php/RantestDB.php b/rantest/web/php/RantestDB.php
new file mode 100644 (file)
index 0000000..3a0f548
--- /dev/null
@@ -0,0 +1,1013 @@
+<?php
+////////////////////////////////////////////////////////////////////////////////
+//
+// File:       RantestDB.php
+// Revision:   1.0
+// Description:        PHP Module to access the rantest database
+// Author:     Andrew@ClearSCm.com
+// Created:    Mon Apr 28 15:20:06 MST 2008
+// Modified:   
+// Language:   PHP
+//
+// (c) Copyright 2008, General Dynamics, all rights reserved.
+//
+// All rights reserved except as subject to DFARS 252.227-7014 of contract
+// number CP02H8901N issued under prime contract N00039-04-C-2009.
+//
+// Warning: This document contains technical data whose export is restricted
+// by the Arms Export Control Act (Title 22, U.S.C., Sec 2751, et seq.) or the
+// Export Administration Act of 1979, as amended, Title, 50, U.S.C., App. 2401
+// et seq. Violations of these export laws are subject to severe criminal
+// penalties. Disseminate in accordance with provisions of DoD Directive
+// 5230.25.
+//
+////////////////////////////////////////////////////////////////////////////////
+include_once ("Utils.php");
+
+// DEBUG Flag
+$debug = 1;
+
+// Read only access
+$userid                = "pswitreader";
+$password      = "reader";
+$dbserver      = "seast1";
+$dbname                = "rantest";
+
+$sortBy                = (empty ($_REQUEST["sortBy"]))    ? "Start"        : $_REQUEST["sortBy"];
+$direction     = (empty ($_REQUEST["direction"])) ? "descending"   : $_REQUEST["direction"];
+$day           = (empty ($_REQUEST["day"]))       ? date ("m/d/Y") : $_REQUEST["day"];
+
+// N/A
+$na = "<font color=#999999>N/A</font>";
+
+$failureTypes = array (
+  "Aborted",
+  "Exit",
+  "Incomplete",
+  "Other",
+  "p2pDataVal",
+  "Parsing",
+  "Timed out",
+  "valMsgs.pl"
+);
+
+$nonFailures = array (
+  "Success",
+  "In progress",
+  "Logging started"
+);
+
+$testTypes = array (
+  "All",
+  "Normal",
+  "Regression",
+  "Run for Record"
+);
+
+function dbError ($msg, $statement) {
+  $errno  = mysql_errno ();
+  $errmsg = mysql_error ();
+
+  print <<<END
+<h2><font color="red">ERROR:</font> $msg</h2>
+<b>Error #$errno:</b><br>
+<blockquote>$errmsg</blockquote>
+<b>SQL Statement:</b><br>
+<blockquote>$statement</blockquote>
+END;
+
+  exit ($errno);
+} // dbError
+
+function openDB () {
+  global $dbserver;
+  global $userid;
+  global $password;
+  global $dbname;
+
+  $db = mysql_connect ($dbserver, $userid, $password)
+    or dbError (__FUNCTION__ . ": Unable to connect to database server $dbserver", "Connect");
+
+  mysql_select_db ($dbname)
+    or dbError (__FUNCTION__ . ": Unable to select the $dbname database", "$dbname");
+} // openDB
+
+function getDays () {
+  $days = array ();
+
+  $statement = "select start from testrun group by left (start, 10) order by start desc";
+
+  $result = mysql_query ($statement)
+    or dbError (__FUNCTION__ . ": Unable to execute query: ", $statement);
+
+  while ($row = mysql_fetch_array ($result)) {
+    array_push ($days, YMD2MDY ($row["start"]));
+  } // while
+
+  return $days;
+} // getDays
+
+function setRowColor ($result) {
+  if ($result == "Success") {
+    return " class=success";
+  } elseif ($result == "Failure") {
+    return " class=failure";
+  } elseif ($result == "Timed out") {
+    return " class=timed_out";
+  } elseif ($result == "In progress" ||
+             $result == "Logging started") {
+    return " class=in_progress";
+  } else {
+    return " class=white";
+  } // if
+} // setRowColor
+
+function colorResult ($result) {
+  if ($result == "Success") {
+    return "<b><font color=green>$result</b></font>";
+  } elseif ($result == "Failure") {
+    return "<b><font color=red>$result</b></font>";
+  } else {
+    return $result;
+  } // if
+} // colorResult
+
+function sortPassed ($a, $b) {
+  global $direction;
+
+  return cmpNbr ($a, $b, "Passed", $direction);
+} // sortPassed
+
+function sortFailed ($a, $b) {
+  global $direction;
+
+  return cmpNbr ($a, $b, "Failed", $direction);
+} // sortFailed
+
+function sortSuccess ($a, $b) {
+  global $direction;
+
+  return cmpNbr ($a, $b, "Success", $direction);
+} // sortSuccess
+
+function sortFailure ($a, $b) {
+  global $direction;
+
+  return cmpNbr ($a, $b, "Failure", $direction);
+} // sortFailure
+
+function sortTotal ($a, $b) {
+  global $direction;
+
+  return cmpNbr ($a, $b, "Total", $direction);
+} // sortTotal
+
+function sortSuite ($a, $b) {
+  global $direction;
+
+  return cmpStr ($a, $b, "Suite", $direction);
+} // sortSuite
+
+function sortStart ($a, $b) {
+  global $direction;
+
+  return cmpStr ($a, $b, "Start", $direction);
+} // sortStart
+
+function sortDate ($a, $b) {
+  global $direction;
+
+  return cmpStr ($a, $b, "Start", $direction);
+} // sortDate
+
+function sortEnd ($a, $b) {
+  global $direction;
+
+  return cmpStr ($a, $b, "End", $direction);
+} // sortEnd
+
+function sortTestcase ($a, $b) {
+  global $direction;
+
+  return cmpStr ($a, $b, "Testcase", $direction);
+} // sortTestcase
+
+function sortType ($a, $b) {
+  global $direction;
+
+  return cmpStr ($a, $b, "Type", $direction);
+} // sortType
+
+function sortUnitVersion ($a, $b) {
+  global $direction;
+
+  return cmpStr ($a, $b, "Unit/Version", $direction);
+} // sortUnitVersion
+
+function sortUnit ($a, $b) {
+  global $direction;
+
+  return cmpStr ($a, $b, "Unit", $direction);
+} // sortUnit
+
+function sortStatus ($a, $b) {
+  global $direction;
+
+  return cmpStr ($a, $b, "Status", $direction);
+} // sortStatus
+
+function sortDuration ($a, $b) {
+  global $direction;
+
+  return cmpNbr ($a, $b, "Duration", $direction);
+} // sortDuration
+
+function getName ($table, $id) {
+  $statement = "select name from $table where id=$id";
+
+  $result = mysql_query ($statement)
+    or dbError (__FUNCTION__ . ": Unable to execute query: ", $statement);
+
+  $row = mysql_fetch_array ($result);
+
+  return $row["name"];
+} // getName
+
+function getTestNames () {
+  $statement = "select name from test order by name";
+
+  $result = mysql_query ($statement)
+    or dbError (__FUNCTION__ . ": Unable to execute query: ", $statement);
+
+  $names = array ("&lt;All Tests&gt;");
+
+  while ($row = mysql_fetch_array ($result)) {
+    array_push ($names, $row["name"]);
+  } // while
+
+  return $names;
+} // getTestNames
+
+function getBuildNbr ($runid) {
+  global $na;
+
+  $statement = "select name from version where type=\"ran_version\" and runid=$runid";
+
+  $result = mysql_query ($statement)
+    or dbError (__FUNCTION__ . ": Unable to execute query: ", $statement);
+
+  $row = mysql_fetch_array ($result);
+
+  if (isset ($row["name"])) {
+    $buildNbr = preg_replace ("/.*\-(\w+)/", "$1", $row["name"]);
+    return $buildNbr;
+  } else {
+    return $na;
+  } // if
+} // getBuildNbr
+
+function getVersion ($type, $runid) {
+  global $na;
+
+  $statement = "select name from version where type=\"$type\" and runid=$runid";
+
+  $result = mysql_query ($statement)
+    or dbError (__FUNCTION__ . ": Unable to execute query: ", $statement);
+
+  $row = mysql_fetch_array ($result);
+
+  if (isset ($row["name"])) {
+    return $row["name"];
+  } else {
+    return $na;
+  } // if
+} // getVersion
+
+function getVersions ($type) {
+  $statement = "select name from version where type=\"$type\" group by name";
+
+  $result = mysql_query ($statement)
+    or dbError (__FUNCTION__ . ": Unable to execute query: ", $statement);
+
+//  $names = array ("&lt;All Versions&gt;");
+  $names = array ();
+
+  while ($row = mysql_fetch_array ($result)) {
+    array_push ($names, $row["name"]);
+  } // while
+
+  return $names;
+} // getVersions
+
+function getTestVersions ($testcase) {
+  if (isset ($testcase)) {
+    $testcase = preg_replace ("/\*/", "%", $testcase);
+    $condition = "and test.name like \"$testcase\"";
+  } else {
+    $condition = "";
+  } // if
+
+  $statement = <<<END
+select
+  testruns.runid       as runid,
+  testrun.start                as start,
+  testruns.end         as end,
+  testruns.unit                as unit,
+  testruns.exectype    as type,
+  test.name            as testcase,
+  status.name          as status,
+  testruns.eastlogs    as eastlogs,
+  version.name         as version
+from
+  testrun,
+  testruns,
+  status,
+  test,
+  version
+where
+  version.runid                = testruns.runid        and
+  test.id              = testruns.tcid         and
+  testrun.id           = testruns.runid        and
+  testruns.statusid    = status.id             and
+  version.type         = "ran_version"
+$condition
+END;
+
+  $result = mysql_query ($statement)
+    or DBError ("Unable to execute query: ", $statement);
+
+  $data = array ();
+
+  while ($row = mysql_fetch_array ($result)) {
+    $line["Testcase"]  = $row["testcase"];
+    $line["Start"]     = $row["start"];
+    $line["Version"]   = $row["version"];
+    $line["End"]       = $row["end"];
+    $line["Unit"]      = $row["unit"];
+    $line["Type"]      = $row["type"];
+    $line["Status"]    = $row["status"];
+    $line["Duration"]  = strtotime ($row["end"]) - strtotime ($row["start"]);
+    $line["Version"]   = $row["version"];
+    $line["_eastlogs"] = $row["eastlogs"];
+    $line["_runid"]    = $row["runid"];
+
+    array_push ($data, $line);
+  } // while
+
+  return $data;
+} // getTestVersions
+
+function getLatestDate () {
+  $statement = "select left (start,10) as start from testrun order by start desc limit 0,1";
+
+  $result = mysql_query ($statement)
+    or dbError (__FUNCTION__ . ": Unable to execute query: ", $statement);
+
+  $row = mysql_fetch_array ($result);
+
+  return $row["start"];
+} // getLastestDate
+
+function getEarliestDate () {
+  $statement = "select left (start,10) as start from testrun order by start limit 0,1";
+
+  $result = mysql_query ($statement)
+    or dbError (__FUNCTION__ . ": Unable to execute query: ", $statement);
+
+  $row = mysql_fetch_array ($result);
+
+  return $row["start"];
+} // getLastestDate
+
+function reportDateDropdown () {
+  global $script, $day;
+
+  $days = getDays ();
+
+  $dateDropdown .= "<select name=day class=inputfield onChange=\"ChangeDay(this.value,'$script');\">";
+
+  if (count ($days) < 2) {
+    $day = $days[0];
+  } elseif (!isset ($day)) {
+    $day = date ("m/d/Y");
+  } // if
+
+  foreach ($days as $d) {
+    $dateDropdown .= "<option";
+
+    if ($d == $day) {
+      $dateDropdown .= " selected=\"selected\"";
+    } // if
+
+    $dateDropdown .= ">$d</option>";
+  } // foreach
+
+  $dateDropdown .= "</select>";
+
+  return $dateDropdown;
+} // reportDateDropdown
+
+function stats ($date = "", $type = "") {
+  if (empty ($date)) {
+    $date = getLatestDate ();
+  } else {
+    $date = MDY2YMD ($date);
+  } // if
+
+  if (empty ($type)) {
+    $type = "All";
+  } // if
+
+  $typecond    = ($type == "All") ? "" : "exectype = \"$type\" and";
+
+  $statement = <<<END
+select
+  count(*)     as count,
+  status.name  as result
+from 
+  testrun,
+  testruns,
+  status
+where
+  left (start,10)      = "$date"               and
+  $typecond
+  testrun.id           = testruns.runid        and
+  testruns.statusid    = status.id
+group by
+  statusid
+END;
+
+  $result = mysql_query ($statement)
+    or dbError (__FUNCTION__ . ": Unable to execute query: ", $statement);
+
+  while ($row = mysql_fetch_array ($result)) {
+    if ($row["count"] == 0) {
+       $stats .= "No $row[result] ";
+    } elseif ($row["count"] == 1) {
+       $stats .= "1 $row[result] ";
+    } else {
+      if ($row["result"] == "Failure") {
+       $stats .= "$row[count] Failures ";
+      } else {
+        $stats .= "$row[count] Successes ";
+      } // if
+    } // if
+  } // while
+
+  return $stats;
+} // stats
+
+function logs ($logs, $forEmail = false) {
+  global $na;
+
+  if (file_exists ($logs)) {
+    if ($forEmail) {
+      return "Yes";
+    } else {
+      // Chop off "/east/seast1"
+      $logs = substr ($logs, 12);
+      return "<a href=\"$logs\"><img border=0 src=\"/images/log.png\" height=20 title=\"Browse to logfiles\"></a>";
+    } // if
+  } else {
+    if ($forEmail) {
+      return "No";
+    } else {
+      return "<img border=0 src=\"/icons/broken.png\" height=20 title=\"Unable to find log file for this test execution - Perhaps the logfiles have aged away\">";
+      return $na;
+    } // if
+  } // if
+} # logs
+
+function getStatus ($startDate, $endDate, $type = "") {
+  $dateCond = "";
+
+  if (isset ($startDate)) {
+    $startDate = MDY2YMD ($startDate);
+  } // if
+
+  if (isset ($endDate)) {
+    $endDate   = MDY2YMD ($endDate);
+  } // if
+
+  if (isset ($startDate) and isset ($endDate)) {
+    $dateCond = "and left (testrun.start, 10) >= \"$startDate\" " .
+                "and left (testruns.end,  10) <= \"$endDate\"" ;
+  } elseif ($startDate) {
+    $dateCond = "and left (testrun.start, 10) >= \"$startDate\"";
+  } elseif ($endDate) {
+    $dateCond = "and left (testruns.end,  10) <= \"$endDate\"";
+  } # if
+
+  $exectypeCond = "";
+
+  if ($type != "" and $type != "All") {
+    $exectypeCond = "and testruns.exectype = \"$type\"";
+  } // if
+
+  $statement = <<<END
+select
+  status.name                  as status,
+  left (testrun.start,10)      as date
+from
+  test,
+  status,
+  testrun,
+  testruns
+where
+  test.id                      = testruns.tcid         and
+  testrun.id                   = testruns.runid        and
+  testruns.statusid            = status.id
+  $dateCond
+  $exectypeCond
+END;
+
+  $result = mysql_query ($statement)
+    or dbError (__FUNCTION__ . ": Unable to execute query: ", $statement);
+
+  while ($row = mysql_fetch_array ($result)) {
+    if (empty ($status{$row["date"]}["Success"])) {
+      $status{$row["date"]}["Success"] = 0;
+    } // if
+    if (empty ($status{$row["date"]}["Failure"])) {
+      $status{$row["date"]}["Failure"] = 0;
+    } // if
+    $status{$row["date"]}{$row["status"]}++;
+    $status{$row["date"]}{Total}++;
+  } // while
+
+  // We used $row["date"] as the key so that we could do the totalling
+  // above but return an array with date inside it.
+  $stats = array ();
+
+  foreach ($status as $key => $value) {
+    $data["Date"]      = $key;
+    $data["Success"]   = $value["Success"];
+    $data["Failure"]   = $value["Failure"];
+    $data["Total"]     = $value["Total"];
+    
+    array_push ($stats, $data);
+  } // foreach
+
+  return $stats;
+} // getStatus
+
+function getStepFailures ($runid) {
+  global $failureTypes, $nonFailures;
+
+  $statement = <<<END
+select
+  step.name            as step,
+  status.name          as status
+from
+  steprun,
+  step,
+  status
+where
+  steprun.stepid       = step.id       and
+  steprun.statusid     = status.id     and
+  runid                        = $runid
+order by
+  step.name
+END;
+
+  $result = mysql_query ($statement)
+    or dbError (__FUNCTION__ . ": Unable to execute query: ", $statement);
+
+  $stepFailures = array ();
+
+  while ($row = mysql_fetch_array ($result)) {
+    // We only care about failures...
+    if (array_search ($row["status"], $nonFailures) !== false) {
+      continue;
+    } // if
+
+    $stepFailure["Step"]       = $row["step"];
+    $stepFailure["Reason"]     = $row["status"];
+    $stepFailure["Count"]      = $row["count"];
+
+    array_push ($stepFailures, $stepFailure);
+  } // while
+
+  return $stepFailures;
+} // getStepFailures
+
+function getFailures ($day) {
+  global $failureTypes;
+
+  $dateCond = "and left (testrun.start, 10) = \"" . MDY2YMD ($day) . "\"";
+
+  $statement = <<<END
+select
+  test.name            as testcase,
+  testruns.unit                as unit,
+  status.name          as status,
+  testrun.start                as timestamp,
+  testruns.runid       as runid
+from
+  test,
+  status,
+  testrun,
+  testruns
+where
+  test.id              = testruns.tcid         and
+  testrun.id           = testruns.runid        and
+  testruns.statusid    = status.id
+  $dateCond
+order by
+  test.name
+END;
+
+  $result = mysql_query ($statement)
+    or dbError (__FUNCTION__ . ": Unable to execute query: ", $statement);
+
+  $data = array ();
+
+  while ($row = mysql_fetch_array ($result)) {
+    // We only care about failures...
+    if ($row["status"] == "Success") {
+      continue;
+    } // if
+
+    $record = array ();
+
+    $record["Testcase"]        = $row["testcase"];
+    $record["Unit"]    = $row["unit"];
+    $record["Time"]    = substr ($row["timestamp"], 11, 8);
+    $record["_runid"]  = $row["runid"];
+    $record["Failures"] = getStepFailures ($row["runid"]);
+
+    array_push ($data, $record);
+  } // while
+
+  return $data;
+} // getFailures
+
+function getTestHistory ($testcase) {
+  if (empty ($testcase)) {
+    $testcase = "%";
+  } else {
+    $testcase = preg_replace ("/\*/", "%", $testcase);
+  } // if
+
+  if ($testcase != "%") {
+    $statement = <<<END
+select
+  testruns.runid       as runid,
+  testrun.start                as start,
+  testruns.end         as end,
+  testruns.unit                as unit,
+  testruns.exectype    as type,
+  test.name            as testname,
+  status.name          as status,
+  testruns.eastlogs    as eastlogs
+from
+  testrun,
+  testruns,
+  status,
+  test
+where
+  test.name            like "$testcase"        and
+  test.id              = testruns.tcid         and
+  testrun.id           = testruns.runid        and
+  testruns.statusid    = status.id
+END;
+  } else {
+    $statement = <<<END
+select
+  test.name                                    as testname,
+  status.name                                  as status,
+  count(if(status.name="Success",1,NULL))      as passed,
+  count(if(status.name="Failure",1,NULL))      as failed,
+  count(*)                                     as total
+from
+  testruns,
+  status,
+  test
+where
+  test.id              = testruns.tcid and 
+  testruns.statusid    = status.id
+group by
+  testname
+END;
+  } // if
+
+  $result = mysql_query ($statement)
+    or DBError ("Unable to execute query: ", $statement);
+
+  $data = array ();
+
+  while ($row = mysql_fetch_array ($result)) {
+    $line["Testcase"]  = $row["testname"];
+
+    if ($testcase != "%") {
+      $line["DUT"]             = $row["unit"];
+      $line["Type"]            = $row["type"];
+      $line["Start"]           = $row["start"];
+      $line["End"]             = $row["end"];
+      $line["Duration"]                = strtotime ($row["end"]) - strtotime ($row["start"]);
+      $line["_eastlogs"]       = $row["eastlogs"];
+      $line["Status"]          = $row["status"];
+      $line["_runid"]          = $row["runid"];
+
+      array_push ($data, $line);
+    } else {
+      $line["Passed"]          = $row["passed"];
+      $line["Failed"]          = $row["failed"];
+      $line["Total"]           = $row["total"];
+      $line["AvgRunTime"]      = averageRunTime ($row["testname"]);
+
+      array_push ($data, $line);
+    } // if
+  } // while
+
+  return $data;
+} // getTestHistory
+
+function getTestcaseVersions ($version) {
+  if ($version == "<All Versions>") {
+    unset ($version);
+  } // if
+
+  if (isset ($version)) {
+    $condition = "and version.name = \"$version\"";
+  } else {
+    $condition = "";
+  } // if
+
+  $statement = <<<END
+select
+  testruns.runid       as runid,
+  testrun.start                as start,
+  testruns.end         as end,
+  testruns.unit                as unit,
+  testruns.exectype    as type,
+  test.name            as testcase,
+  status.name          as status,
+  version.name         as version
+from
+  testrun,
+  testruns,
+  status,
+  test,
+  version
+where
+  version.runid                = testruns.runid        and
+  test.id              = testruns.tcid         and
+  testrun.id           = testruns.runid        and
+  testruns.statusid    = status.id
+$condition
+END;
+
+  $result = mysql_query ($statement)
+    or DBError ("Unable to execute query: ", $statement);
+
+  $data = array ();
+
+  while ($row = mysql_fetch_array ($result)) {
+    $line["Testcase"]  = $row["testcase"];
+    $line["Start"]     = $row["start"];
+    $line["End"]       = $row["end"];
+    $line["Unit"]      = $row["unit"];
+    $line["Type"]      = $row["type"];
+    $line["Status"]    = $row["status"];
+    $line["Duration"]  = strtotime ($row["end"]) - strtotime ($row["start"]);
+    $line["Version"]   = $row["version"];
+    $line["_runid"]    = $row["runid"];
+
+    array_push ($data, $line);
+  } // while
+
+  return $data;
+} // getTestcaseVersions
+
+function averageRunTime ($name) {
+  $statement = <<<END
+select
+  testrun.start                as start,
+  testruns.end         as end
+from
+  test,
+  testrun,
+  testruns
+where
+  test.id      = testruns.tcid         and
+  testrun.id   = testruns.runid        and
+  test.name    = "$name"
+END;
+
+  $result = mysql_query ($statement)
+    or dbError (__FUNCTION__ . ": Unable to execute query: ", $statement);
+
+  $i = 0;
+
+  while ($row = mysql_fetch_array ($result)) {
+    $duration = (strtotime ($row["end"]) - strtotime ($row["start"]));
+
+    $total += $duration;
+    $i++;
+  } // while
+
+  return round ($total / $i);
+} // averageRunTime
+
+function getSuiteruns ($id) {
+  global $sortBy, $direction;
+
+  $statement = <<<END
+select
+  status.name as Status,
+  Start,
+  End
+from
+  suiterun,
+  status
+where
+  suiteid      = $id and
+  statusid     = status.id
+END;
+
+  $result = mysql_query ($statement)
+    or dbError (__FUNCTION__ . ": Unable to execute query: ", $statement);
+
+  $data = array ();
+
+  while ($row = mysql_fetch_array ($result)) {
+    $line["Status"]    = $row["Status"];
+    $line["Start"]     = $row["Start"];
+    $line["End"]       = $row["End"];
+    $line[Duration]    = strtotime ($row["End"]) - strtotime ($row["Start"]);
+
+    array_push ($data, $line);
+  } // while
+
+  // Sort data
+  if ($sortBy == "Status") {
+    uasort ($data, "sortStatus");
+  } elseif ($sortBy == "End") {
+    uasort ($data, "sortEnd");
+  } elseif ($sortBy == "Duration") {
+    uasort ($data, "sortDuration");
+  } else {
+    uasort ($data, "sortStart");
+  } // if
+
+  return $data;
+} // getSuiteruns
+
+function getTestSteps ($runid) {
+  $data["_header"]["DUTVersion"]       = getVersion ("ran_version",            $runid);
+  $data["_header"]["EASTVersion"]      = getVersion ("east_version",           $runid);
+  $data["_header"]["TM500Version"]     = getVersion ("tm500_version",          $runid);
+  $data["_header"]["NMSVersion"]       = getVersion ("nms_version",            $runid);
+  $data["_header"]["RANTESTVersion"]   = getVersion ("rantest_version",        $runid);
+
+  $statement = <<<END
+select
+  steprun.runid                as _runid,
+  step.name            as step,
+  left (start, 10)     as date,
+  start,
+  steprun.end          as end,
+  status.name          as status,
+  eastlogs             as _eastlogs,
+  userid
+from
+  steprun,
+  step,
+  testruns,
+  status
+where
+  steprun.runid        = $runid                and
+  steprun.runid        = testruns.runid        and
+  step.id      = steprun.stepid        and
+  status.id    = steprun.statusid
+order by
+  start
+END;
+
+  $result = mysql_query ($statement)
+    or dbError (__FUNCTION__ . ": Unable to execute query: ", $statement);
+
+  $data["_header"]["userid"]   = "Unknown";
+  $data["_steps"]              = array ();
+
+  while ($row = mysql_fetch_array ($result)) {
+    $line["Step"]                      = $row["step"];
+    $line["Status"]                    = $row["status"];
+    $data["_header"]["Date"]           = $row["date"];
+    $line["Start"]                     = $row["start"];
+    $line["End"]                       = $row["end"];
+    $line["Duration"]                  = strtotime ($row["end"]) - strtotime ($row["start"]);
+    $data["_header"]["userid"]         = $row["userid"];
+    $data["_header"]["_eastlogs"]      = $row["_eastlogs"];
+
+    array_push ($data[_steps], $line);
+  } // while
+
+  return $data;
+} // getTestSteps
+
+function getTestRunTimestamp ($runid) {
+  $statement = <<<END
+select
+  left (start, 10)     as startDate,
+  right (start, 8)     as startTime
+from
+  steprun
+where
+  runid = $runid
+END;
+
+  $result = mysql_query ($statement)
+    or dbError (__FUNCTION__ . ": Unable to execute query: ", $statement);
+
+  $row = mysql_fetch_array ($result);
+
+  return $row["startDate"] . "." . $row["startTime"];
+} // getTestRunTimestamp
+
+function getTestRuns ($day, $type, $export = false) {
+  global $sortBy, $direction;
+
+  $typecond = ($type == "All") ? "" : "exectype = \"$type\" and";
+
+  $statement = <<<END
+select
+  test.name            as testcase,
+  status.name          as status,
+  suite.name           as suite,
+  testrun.start                as start,
+  testruns.runid       as _runid,
+  testruns.end         as end,
+  testruns.unit                as unit,
+  testruns.exectype    as exectype,
+  testruns.eastlogs    as _eastlogs,
+  testruns.suiteid     as _suiteid
+from
+  testruns,
+  testrun,
+  test,
+  status,
+  suite
+where
+  left (start, 10)     = "$day"        and
+  $typecond
+  testruns.runid       = testrun.id    and
+  testruns.tcid                = test.id       and
+  statusid             = status.id     and
+  (suiteid             = suite.id      or
+   suiteid             = 0)
+group by
+concat(tcid, start)
+END;
+
+  $result = mysql_query ($statement)
+    or dbError (__FUNCTION__ . ": Unable to execute query: ", $statement);
+
+  $data = array ();
+  $i   = 1;
+
+  while ($row = mysql_fetch_array ($result)) {
+    if (!$export) {
+      $line["#"] = $i++;
+    } // if
+
+    $line["Suite"]             = $row["suite"];
+    $line["Testcase"]          = $row["testcase"];
+    $line["Type"]              = $row["exectype"];
+    $line["Unit/Version"]      = "$row[unit]-" . getBuildNbr ($row["_runid"]);
+    $line["Logs"]              = (file_exists ($row["eastlogs"])) ? "yes" : "no";
+    $line["Status"]            = $row["status"];
+    $line["Start"]             = substr ($row["start"], 11);
+    $line["End"]               = substr ($row["end"], 11);
+    $line["Duration"]          = strtotime ($row["end"]) - strtotime ($row["start"]);
+
+    $line["_runid"]            = $row["_runid"];
+    $line["_eastlogs"]         = $row["_eastlogs"];
+    $line["_suiteid"]          = $row["_suiteid"];
+
+    array_push ($data, $line);
+  } // while
+
+  // Sort data
+  if ($sortBy == "Suite") {
+    uasort ($data, "sortSuite");
+  } elseif ($sortBy == "Testcase") {
+    uasort ($data, "sortTestcase");
+  } elseif ($sortBy == "Type") {
+    uasort ($data, "sortType");
+  } elseif ($sortBy == "Unit") {
+    uasort ($data, "sortUnitVersion");
+  } elseif ($sortBy == "Status") {
+    uasort ($data, "sortStatus");
+  } elseif ($sortBy == "End") {
+    uasort ($data, "sortEnd");
+  } elseif ($sortBy == "Duration") {
+    uasort ($data, "sortDuration");
+  } else {
+    uasort ($data, "sortStart");
+  } // if
+
+  return $data;
+} // getTestRuns
+?>
diff --git a/rantest/web/php/Utils.php b/rantest/web/php/Utils.php
new file mode 100644 (file)
index 0000000..fcbde7a
--- /dev/null
@@ -0,0 +1,376 @@
+<?php
+////////////////////////////////////////////////////////////////////////////////
+//
+// File:       Utils.php
+// Description:        Utility funcitons
+// Author:     Andrew@ClearSCm.com
+// Created:    Mon Apr 28 15:20:06 MST 2008
+// Modified:   
+// Language:   PHP
+//
+// (c) Copyright 2008, General Dynamics, all rights reserved.
+//
+// All rights reserved except as subject to DFARS 252.227-7014 of contract
+// number CP02H8901N issued under prime contract N00039-04-C-2009.
+//
+// Warning: This document contains technical data whose export is restricted
+// by the Arms Export Control Act (Title 22, U.S.C., Sec 2751, et seq.) or the
+// Export Administration Act of 1979, as amended, Title, 50, U.S.C., App. 2401
+// et seq. Violations of these export laws are subject to severe criminal
+// penalties. Disseminate in accordance with provisions of DoD Directive
+// 5230.25.
+//
+////////////////////////////////////////////////////////////////////////////////
+// Constants
+$VERSION = "1.2.4";
+
+$webdir        = dirname  ($_SERVER["PHP_SELF"]);
+
+function debug ($msg) {
+  global $debug;
+
+  if ($debug == 1) {
+    print "<font color=red>DEBUG:</font> $msg<br>";
+  } // if
+} // debug
+
+function dumpObject ($object) {
+  print "<pre>";
+  print_r ($object);
+  print "</pre>";
+} // dumpObject
+
+function Error ($msg) {
+  print "<font color=red><b>ERROR:</b></font> $msg<br>";
+} // Error
+
+function banner () {
+  $banner = <<<END
+<table class="banner" width="100%" border="0" cellpadding="0" cellspacing="0">
+  <tr>
+    <td class="bannerLeft"><a href="/Rantest">
+    <img src="/images/companyLogo.gif" alt="General Dynamics C4 Systems" border="0"></a></td>
+    <td class="bannerRight">&nbsp;</td>
+  </tr>
+</table>
+END;
+
+  return $banner;
+} // banner
+
+function YMD2MDY ($date) {
+  return substr ($date, 5, 2) . "/" .
+         substr ($date, 8, 2) . "/" .
+         substr ($date, 0, 4);
+} // YMD2MDY
+
+function MDY2YMD ($date) {
+  return substr ($date, 6, 4) . "-" .
+         substr ($date, 0, 2) . "-" .
+         substr ($date, 3, 2);
+} // MDY2YMD
+
+function cmpStr ($a, $b, $field, $direction) {
+  if ($direction == "ascending") {
+    return strcmp ($a[$field], $b[$field]);
+  } else {
+    return strcmp ($b[$field], $a[$field]);
+  } // if
+} // cmpStr
+
+function cmpNbr ($a, $b, $field, $direction) {
+  if ($a[$field] == $b[$field]) {
+    return 0;
+  } // if
+
+  if ($direction == "ascending") {
+    return ($a[$field] < $b[$field]) ? -1 : 1;
+  } else {
+    return ($a[$field] < $b[$field]) ? 1 : -1;
+  } // if
+} // cmpNbr
+
+function getUsername ($userid) {
+  exec ("ypmatch $userid passwd | cut -f5 -d:", &$username);
+
+  if (empty ($username)) {
+    return "Unknown";
+  } else {
+    if ($username[0] == "SWIT account for SWIT testing") {
+      return "pswit";
+    } else {
+      return $username[0];
+    } // if
+  } // if
+} // getUsername
+
+function emailUsersDropdown () {
+  define (DIM, "#888");
+
+  exec ("ypcat passwd | grep ^p|cut -f1,5 -d: | grep -v 'SWIT account'", &$userids);
+
+  $users ["unknown"] = "&lt;Select User&gt;";
+
+  foreach ($userids as $user) {
+    list ($pnbr, $name) = explode (":", $user);
+
+    $users [$pnbr] = $name;
+  } # foreach
+
+  asort ($users);
+
+  $dropdown = "Email to <select name=user class=inputfield style=\"color: " . DIM . "\">";
+  
+  foreach ($users as $pnbr => $name) {
+    $dropdown .= "<option value=\"$pnbr:$name\" style=\"color: ";
+
+    if ($pnbr != "unknown") {
+      $dropdown .= "black";
+    } else {
+      $dropdown .= DIM;
+    } // if
+
+    $dropdown .= "\">$name</option>";
+  } // foreach
+
+  $dropdown .= "</select>";
+
+  return $dropdown;
+} // emailUsersDropdown
+
+function copyright () {
+  global $VERSION;
+
+  $year = date ("Y");
+
+  $thisFile     = "$_SERVER[DOCUMENT_ROOT]/$_SERVER[PHP_SELF]";
+  $lastModified = date ("F d Y @ g:i a", filemtime ($thisFile));
+
+  print <<<END
+<div class=copyright>
+Rantest Web Version: <a href="ChangeLog.php#$VERSION">$VERSION</a> - EAST Automation Team<br>
+Last Modified: $lastModified<br>
+Copyright $year &copy; General Dynamics, all rights reserved<br>
+<a href="/"><img border=0 src="/images/HomeSmall.gif">Home</a>
+&nbsp;|&nbsp;
+<a href="http://ranweb/dokuwiki/doku.php">Wiki</a>
+&nbsp;|&nbsp;
+<a href="http://ranweb/dokuwiki/doku.php?id=ran:ran_test_automation_users_guide">Users Guide</a>
+&nbsp;|&nbsp;
+<a href="http://ranweb/dokuwiki/doku.php?id=ran:rantest">Usage</a>
+&nbsp;|&nbsp;
+<a href="/docs">Other Docs</a><br>
+</div>
+END;
+} // copyright
+
+function Today2SQLDatetime () {
+  return date ("Y-m-d H:i:s");
+} // Today2SQLDatetime
+
+function FormatDuration ($difference) {
+  $seconds_per_min  = 60;
+  $seconds_per_hour = 60 * $seconds_per_min;
+  $seconds_per_day  = $seconds_per_hour * 24;
+
+  $days    = 0;
+  $hours   = 0;
+  $minutes = 0;
+  $seconds = 0;
+
+  if ($difference > $seconds_per_day) {
+    $days       = (int) ($difference / $seconds_per_day);
+    $difference = $difference % $seconds_per_day;
+  } // if
+
+  if ($difference > $seconds_per_hour) {
+    $hours      = (int) ($difference / $seconds_per_hour);
+    $difference = $difference % $seconds_per_hour;
+  } // if
+
+  if ($difference > $seconds_per_min) {
+    $minutes    = (int) ($difference / $seconds_per_min);
+    $difference = $difference % $seconds_per_min;
+  } // if
+
+  $seconds = $difference;
+
+  $day_str  = "";
+  $hour_str = "";
+  $min_str  = "";
+  $sec_str  = "";
+  $duration = "";
+
+  if ($days > 0) {
+    $day_str  = $days == 1 ? "1 day" : "$hours days";
+    $duration = $day_str;
+  } // if
+
+  if ($hours > 0) {
+    $hour_str = $hours == 1 ? "1 hr" : "$hours hrs";
+
+    if ($duration != "") {
+      $duration .= " ". $hour_str;
+    } else {
+      $duration = $hour_str;
+    } // if
+  } // if
+
+  if ($minutes > 0) {
+    $min_str = $minutes == 1 ? "1 min" : "$minutes mins";
+
+    if ($duration != "") {
+      $duration .= " " . $min_str;
+    } else {
+      $duration = $min_str;
+    } // if
+  } // if
+
+  if ($seconds > 0) {
+    $sec_str = $seconds == 1 ? "1 sec" : "$seconds secs";
+
+    if ($duration != "") {
+      $duration .= " " . $sec_str;
+    } else {
+      $duration = $sec_str;
+    } // if
+  } // if
+
+  if ($duration == "" and $seconds == 0) {
+    $duration = "under 1 second";
+  } // if
+
+  return $duration;
+} // FormatDuration
+
+function Duration ($start, $end) {
+  $start_timestamp = strtotime ($start);
+  $end_timestamp   = strtotime ($end);
+
+  $difference = $end_timestamp - $start_timestamp;
+
+  return FormatDuration ($difference);
+} // Duration
+
+// Returns a string representation of a CSV file given a hash
+// representing the data. If $title is supplied it is made the first
+// line. Next a header row is generated based on the keys of the $data
+// hash. Finally data rows are generated. Any key in the hash
+// beginning with "_" is skipped.
+function exportCSV ($data, $title = "") {
+  if (isset ($title)) {
+    $csv .= "$title\n";
+  } // if
+
+  // Create header line
+  $firstTime = true;
+
+  foreach ($data[0] as $key => $value) {
+    // Skip "hidden" fields - fields beginning with "_"
+    if (preg_match ("/^_/", $key) == 1) {
+      continue;
+    } // if
+
+    if (!$firstTime) {
+      $csv .= ",";
+    } else {
+      $firstTime = false;
+    } // if
+
+    $csv .= "\"$key\"";
+  } // foreach
+
+  $csv .= "\n";
+
+  // Data lines
+  foreach ($data as $entry) {
+    $firstTime = true;
+
+    foreach ($entry as $key => $value) {
+      // Skip "hidden" fields - fields beginning with "_"
+      if (preg_match ("/^_/", $key) == 1) {
+       continue;
+      } // if
+
+      if (!$firstTime) {
+       $csv .= ",";
+      } else {
+       $firstTime = false;
+      } // if
+
+      $csv .= "\"$value\"";
+    } // foreach
+
+    $csv .= "\n";
+  } // foreach
+
+  return $csv;
+} // exportCSV
+
+function getStyleSheets () {
+  $styleSheet = "\n<style type=\"text/css\">\n";
+
+  foreach (file ("$_SERVER[DOCUMENT_ROOT]/css/Testing.css") as $line) {
+    $styleSheet .= $line;
+  } // foreach
+
+  $styleSheet .= "\n";
+
+  foreach (file ("$_SERVER[DOCUMENT_ROOT]/css/Tables.css") as $line) {
+    $styleSheet .= $line;
+  } // foreach
+
+  $styleSheet .= "\n</style>\n";
+
+  return $styleSheet;
+} // getStyleSheets
+
+function mailReport ($pnbr, $username, $subject, $body, $filename, $attachment) {
+  if ($pnbr == "unknown") {
+    return "<p><span class=error>ERROR:</span> You need to select a user to email to first!</p>";
+  } // if    
+
+  $mimeSeparator = md5 (time ());
+
+  $to       = "$username <$pnbr@gdc4s.com>";
+  $toDisplay= "$username &lt;<a href=\"mailto:$pnbr@gdc4s.com\">$pnbr@gdc4s.com</a>&gt;";
+
+  $headers  = "From: RanTestWeb@gdc4s.com\n";
+  $headers .= "MIME-Version: 1.0\n";
+  $headers .= "X-Mailer: PHP\n";
+  $headers .= "Content-Type: multipart/mixed;\n";
+  $headers .=" boundary=\"$mimeSeparator\"";
+
+  $message  = "This is a multi-part message in MIME format.\n";
+  $message .= "--$mimeSeparator\n";
+  $message .= "Content-Type: text/html; charset=ISO-8859-1\n";
+  $message .= "Content-Transfer-Encoding: 7bit\n\n";
+
+  $message .= <<<END
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+END;
+
+  $message .= getStyleSheets ();
+  $message .= <<<END
+</head>
+<body>
+<h1 align="center">$subject</h1>
+END;
+  $message .= $body;
+
+  $message .= "\n--$mimeSeparator\n";
+  $message .= "Content-Type: text/vnd.ms-excel; name=\"$filename\"\n";
+  $message .= "Content-Disposition: inline; filename=\"$filename\"\n\n";
+  $message .= $attachment;
+  $message .= "\n\n--$mimeSeparator\n";
+
+  if (mail ($to, $subject, $message, $headers) == false) {
+    return "<p><span class=error>ERROR:</span> Unable to send email to $to</p>";
+  } else {
+    return "<p>Email sent to $toDisplay</p>";
+  } // if
+} // mailReport
+?>
diff --git a/rantest/web/php/exportToCVS.php b/rantest/web/php/exportToCVS.php
new file mode 100644 (file)
index 0000000..54943ef
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+////////////////////////////////////////////////////////////////////////////////
+//
+// File:       exportToCVS.php
+// Description:        Exports a table to CVS
+// Author:     Andrew@ClearSCm.com
+// Created:    Mon Apr 28 15:20:06 MST 2008
+// Modified:   
+// Language:   PHP
+//
+// (c) Copyright 2000-2008, General Dynamics, all rights reserved.
+//
+////////////////////////////////////////////////////////////////////////////////
+function exportToCSV ($filename, $data) {
+  header ("Content-Type: application/octect-stream");
+  header ("Content-Disposition: attachment; filename=$filename");
+
+  foreach ($data as $line) {
+    foreach ($line as $key => $value) {
+      if (!$first_time) {
+       print ",\"$value\"";
+      } else {
+       print "\"$value\"";
+      } // if
+    } // foreach
+  } // foreach
+} // exportToCSV