541 lines
16 KiB
Perl
541 lines
16 KiB
Perl
|
#
|
||
|
# created: 5/4/99, a-jbilas
|
||
|
# modified:
|
||
|
#
|
||
|
|
||
|
if (!$__IITBUILDPM ) { use iit::build; } #TODO: <- fix dependencies so only iit::env is needed
|
||
|
use Win32::OLE;
|
||
|
|
||
|
####################################################################################
|
||
|
|
||
|
# BeginBVT()
|
||
|
|
||
|
# Performs startup tasks for beginning a BVT script
|
||
|
|
||
|
# a-jbilas, 06/09/99 - created
|
||
|
|
||
|
####################################################################################
|
||
|
|
||
|
sub BeginBVT
|
||
|
{
|
||
|
|
||
|
# our way of doing set local (reset at EndBuild())
|
||
|
$sOldPath = $PATH;
|
||
|
$sOldInclude = $INCLUDE;
|
||
|
$sOldLib = $LIB;
|
||
|
|
||
|
# combine all parameters into one big list of allowed arguments
|
||
|
push(@lAllowedArgs, @lAllowedModifiers);
|
||
|
push(@lAllowedArgs, @lAllowedLanguages);
|
||
|
push(@lAllowedArgs, @lAllowedBuilds);
|
||
|
push(@lAllowedArgs, @lAllowedComponents);
|
||
|
|
||
|
# parse the args and put the results in @lArgs (ParseArgs also sets $sBVTBuildNumber automagically)
|
||
|
BuildAcceleratorLists();
|
||
|
@lArgs = ParseArgs(@_);
|
||
|
|
||
|
# much of this stuff is duplicated from BeginBuild() (which we can't use because it does too
|
||
|
# many build specific routines)
|
||
|
$bNoCopy = IsMemberOf("NOCOPY", @lArgs);
|
||
|
$nTimePasses = 10; # if TIME param passed, the average of this number of passes will be recorded
|
||
|
|
||
|
$bErrorConcat = 1; # activate manual error handling
|
||
|
|
||
|
$strBuildMsg .= "<! ".$sShortBuildName." ".$nScriptStartTime." SUMMARY ENTRY POINT >\n";
|
||
|
|
||
|
if (IsMemberOf("VERBOSE", @lArgs))
|
||
|
{
|
||
|
$DEBUG = 1; #TODO: change all refs to this flag to $bVerbose
|
||
|
$bVerbose = 1;
|
||
|
}
|
||
|
$bLocal = IsMemberOf("LOCAL", @lArgs);
|
||
|
|
||
|
# buildnumber was set by SetLocalGlobalsAndBegin() and/or ParseArgs()
|
||
|
if ($sBuildNumber eq "0000")
|
||
|
{
|
||
|
PrintL("\nNo build number specified, assuming today's build\n", PL_NOLOG);
|
||
|
$sBuildNumber = GetBuildNumber();
|
||
|
}
|
||
|
|
||
|
if (lc($USERNAME) eq $sOfficialBuildAccount || IsMemberOf("OFFICIAL", @lArgs))
|
||
|
{
|
||
|
$bOfficialBuild = 1;
|
||
|
$sDropDir =~ s/\\0000\\/\\$sBuildNumber\\/; #set the drop dir location for official build
|
||
|
$sLogDropDir =~ s/0000/$sBuildNumber/; #set the drop dir location for official build
|
||
|
$sRemoteBuildLog =~ s/0000/$sBuildNumber/; #set the log name for official build
|
||
|
$sBuildLog =~ s/0000/$sBuildNumber/; #set the log name for official build
|
||
|
if (!IsMemberOf(@lArgs, "QUIET") && IsMemberOf(@lAllowedArgs, "QUIET"))
|
||
|
{
|
||
|
push(@lArgs, "QUIET");
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
$bOfficialBuild = 0;
|
||
|
$sDropDir =~ s/$sRootDropDir/$sTestRootDropDir/;
|
||
|
$sLogDropDir = $sLogDir;
|
||
|
$sRemoteBuildLog = $sBuildLog;
|
||
|
$sRootDropDir = $sTestRootDropDir;
|
||
|
}
|
||
|
|
||
|
@lModifiers = Intersect(*lAllowedModifiers, *lArgs);
|
||
|
|
||
|
# intersect the @lArgs with each 'Allowed' list to categorize the parameters we need to support
|
||
|
|
||
|
if (IsMemberOf("ALLCOMP", @lModifiers))
|
||
|
{
|
||
|
@lComponents = @lAllowedComponents;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
@lComponents = Intersect(*lAllowedComponents, *lArgs);
|
||
|
if (@lComponents == ())
|
||
|
{
|
||
|
PrintL("\nNo build types specified, assuming $lAllowedComponents[0] component\n");
|
||
|
@lComponents = ($lAllowedComponents[0]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@lBuilds = Intersect(*lAllowedBuilds, *lArgs);
|
||
|
if (@lBuilds == ())
|
||
|
{
|
||
|
PrintL("\nNo build types specified, assuming $lAllowedBuilds[0] buildtype\n");
|
||
|
@lBuilds = ($lAllowedBuilds[0]);
|
||
|
}
|
||
|
|
||
|
if (IsMemberOf("ALLLANG", @lModifiers))
|
||
|
{
|
||
|
@lLanguages = @lAllowedLanguages;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
@lLanguages = Intersect(*lAllowedLanguages, *lArgs);
|
||
|
if (@lLanguages == () && (@lAllowedLanguages != ()))
|
||
|
{
|
||
|
PrintL("\nNo languages specified, assuming ".$shtolonglang{lc($lAllowedLanguages[0])}." language\n");
|
||
|
@lLanguages = ($lAllowedLanguages[0]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
# open the log file
|
||
|
# the BVT log name contains the build number (so that multiple builds may be run from the same directory)
|
||
|
# leave the BVT log in the current working directory
|
||
|
|
||
|
EchoedUnlink($sMailfile, $sVarsLog, $sTyposLog, $sSyncLog, $sUpdateLog, $sBuildLog);
|
||
|
|
||
|
$fhBuildLog = StartLog($sBuildLog, 1, ($bOfficialBuild && $bWin98));
|
||
|
|
||
|
OutputHeader($nLoggingMode);
|
||
|
|
||
|
if (defined &BeginBVTCustom)
|
||
|
{
|
||
|
return(BeginBVTCustom() && $rc);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return($rc);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
####################################################################################
|
||
|
|
||
|
# EndBVT()
|
||
|
|
||
|
# Performs cleanup tasks for ending a BVT script
|
||
|
|
||
|
# a-jbilas, 06/09/99 - created
|
||
|
|
||
|
####################################################################################
|
||
|
|
||
|
sub EndBVT()
|
||
|
{
|
||
|
if (defined &EndBVTCustom)
|
||
|
{
|
||
|
EndBVTCustom();
|
||
|
}
|
||
|
|
||
|
OutputFooter($nLoggingMode);
|
||
|
$strBuildMsg .= "<\/body>";
|
||
|
|
||
|
if ($nBVTFailureCount)
|
||
|
{
|
||
|
print("\nOne or more BVTs failed\n");
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
print("\nAll BVTs PASS!\n");
|
||
|
}
|
||
|
|
||
|
$fhMailFile = OpenFile($sMailfile, "write");
|
||
|
if ($fhMailFile)
|
||
|
{
|
||
|
my($tmpBuildMsg) = $strBuildMsg;
|
||
|
my($replStr) = "<BR><BR><b><font size=5>".$sBuildName." ".BuildCodeToHTML($bcStatus)."<\/b>".
|
||
|
" - <a href=\"".TranslateToHTTP($sRemoteBuildLog)."\">Log<\/a><\/font><BR><BR>";
|
||
|
$strBuildMsg =~ s/<\! $sShortBuildName $nScriptStartTime SUMMARY ENTRY POINT >/$replStr/;
|
||
|
$fhMailFile->print($strBuildMsg);
|
||
|
CloseFile($fhMailFile);
|
||
|
$strBuildMsg = $tmpBuildMsg;
|
||
|
}
|
||
|
|
||
|
CloseFile($fhBuildLog);
|
||
|
if ($nLoggingMode == 2)
|
||
|
{
|
||
|
InsertSummaryIntoLog($sBuildLog);
|
||
|
}
|
||
|
|
||
|
if (!$bNoCopy && ($sRemoteBuildLog ne "") & $bOfficialBuild)
|
||
|
{
|
||
|
if (!CopyLogs())
|
||
|
{
|
||
|
PrintL("Warning: ".$sShortBuildName." log failed to copy\n", PL_MSGONLY);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!$bOfficialBuild && !IsMemberOf("QUIET", @lArgs))
|
||
|
{
|
||
|
PrintL("\n - Launching BVT log (use 'QUIET' option next time to skip)\n");
|
||
|
Execute($sBuildLog);
|
||
|
PrintL("\n");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
sub StartLog($;$$)
|
||
|
{
|
||
|
my($logname, $useDHTML, $updateTOC) = @_;
|
||
|
|
||
|
$fhNewLog = OpenFile($_[0], "write");
|
||
|
if (!$fhNewLog)
|
||
|
{
|
||
|
PrintL("Could not open ".$logname.", logging for this session will be disabled\n\n", PL_BIGERROR);
|
||
|
return("");
|
||
|
}
|
||
|
elsif ($useDHTML)
|
||
|
{
|
||
|
$fhNewLog->print(GetAllTextFromFile($sDHTMLIncFile)."\n\n<html>\n");
|
||
|
if ($strBuildMsg !~ /<! DHTML ACTIVATION SCRIPT >/)
|
||
|
{
|
||
|
PrintL("<! DHTML ACTIVATION SCRIPT >".GetAllTextFromFile($sDHTMLIncFile)
|
||
|
."<! END DHTML ACTIVATION SCRIPT >", PL_MSGONLY);
|
||
|
}
|
||
|
$bDHTMLActive = 1;
|
||
|
|
||
|
if ($_[2])
|
||
|
{
|
||
|
UpdateLogTOC($sRemoteTOC, $sBuildLog);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return($fhNewLog);
|
||
|
}
|
||
|
|
||
|
####################################################################################
|
||
|
|
||
|
# DoBVTs()
|
||
|
|
||
|
# Do the BVTs
|
||
|
|
||
|
# a-jbilas, 06/09/99 - created
|
||
|
|
||
|
####################################################################################
|
||
|
|
||
|
sub DoBVTs
|
||
|
{
|
||
|
# define and categorize all supported parameters
|
||
|
# each category must have at least one option selected to run a BVT (except @lAllowedModifiers and @lAllowedLanguages))
|
||
|
|
||
|
SetBVTDependentVars(); # we really shouldn't be calling this yet, but we need to init the component list
|
||
|
foreach $component (keys(%hStandardBVTs))
|
||
|
{
|
||
|
push(@lAllowedComponents, $component);
|
||
|
}
|
||
|
|
||
|
%hOptionDescription = (%hOptionDescription,
|
||
|
# <----------------------------- SCREEN WIDTH ------------------------------------->
|
||
|
("Local" => " get files from local SLM project"),
|
||
|
("SMoke" => " include smoketests - smoketests failures will not fail runbvt"),
|
||
|
("SMokeOnly" => "run only smoketests - smoketests failures will fail runbvt"));
|
||
|
|
||
|
$sBuildName = "BVT";
|
||
|
$nBVTFailureCount = 0;
|
||
|
|
||
|
$sExcelPerfDoc = $sLogDropDir."\\performance\\".$sShortBuildName."perf.xls";
|
||
|
$sExcelPerfDocWWW = $sLogDropDir."\\performance\\".$sShortBuildName."perf.recorded.xls";
|
||
|
|
||
|
local($bLocal) = 0; # use local copies of files (see hash table above)
|
||
|
|
||
|
BeginBVT(@_);
|
||
|
|
||
|
if (@lLanguages == ())
|
||
|
{
|
||
|
@lLanguages = ("");
|
||
|
}
|
||
|
|
||
|
# run the BVTs
|
||
|
foreach $language (@lLanguages)
|
||
|
{
|
||
|
foreach $component (@lComponents)
|
||
|
{
|
||
|
foreach $buildType (@lBuilds)
|
||
|
{
|
||
|
my($bBVTRC) = 1;
|
||
|
if (!IsMemberOf("SMOKEONLY", @lArgs))
|
||
|
{
|
||
|
$bBVTRC = RunBVTSingle(\%hStandardBVTs, $component, $shtolonglang{lc($language)}, $buildType);
|
||
|
}
|
||
|
|
||
|
if (($bBVTRC && IsMemberOf("SMOKE", @lArgs)) || IsMemberOf("SMOKEONLY", @lArgs))
|
||
|
{
|
||
|
my($ret) = RunBVTSingle(\%hSmokeBVTs, $component, $shtolonglang{lc($language)}, $buildType);
|
||
|
if (!$ret && IsMemberOf("SMOKEONLY", @lArgs))
|
||
|
{
|
||
|
$bBVTRC = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!$bBVTRC)
|
||
|
{
|
||
|
$bcStatus |= BC_FAILED;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
EndBVT();
|
||
|
|
||
|
return(!($bcStatus & BC_FAILED));
|
||
|
}
|
||
|
|
||
|
sub RunBVTSingle
|
||
|
{
|
||
|
# shared 'global' variables between build functions
|
||
|
local($hConfig, $sBVTComponent, $sBVTLanguage, $sBVTType) = @_;
|
||
|
|
||
|
local($sBVTBuildNumber) = $sBuildNumber;
|
||
|
local($bCopyFailed) = 0;
|
||
|
local($nDiffNumber) = 0;
|
||
|
local($rc) = 1;
|
||
|
local($sShBVTLanguage) = $longtoshlang{lc($sBVTLanguage)};
|
||
|
|
||
|
local(@lNeededFiles) = (); #files that are needed to run BVT
|
||
|
local(@lCopiedFiles) = (); #needed files that were copied to run BVT
|
||
|
|
||
|
ResetError(); # reset the error buffer
|
||
|
|
||
|
$strLanguage = $sShBVTLanguage; # BVTCompare() support
|
||
|
|
||
|
if ($sBVTBuildNumber eq GetBuildNumber())
|
||
|
{
|
||
|
$sBVTBuildNumber = GetBuildNumber($hConfig->{$sBVTComponent}->{"BuildStartYear"});
|
||
|
}
|
||
|
|
||
|
SetBVTDependentVars();
|
||
|
|
||
|
# just to make refs a bit less painful (for new refs: hComponent->{"Element"})
|
||
|
local(*hComponent) = $hConfig->{$sBVTComponent};
|
||
|
|
||
|
$sDropDir = hComponent->{"RemoteSrcDir"};
|
||
|
|
||
|
# check to see if we should attempt to run the BVT
|
||
|
|
||
|
if ($hConfig->{$sBVTComponent} eq "")
|
||
|
{
|
||
|
PrintL("Component does not exist: ".$sBVTComponent, PL_VERBOSE);
|
||
|
return(1);
|
||
|
}
|
||
|
if ((!IsMemberOf($sShBVTLanguage, split(/ +/, hComponent->{"Languages"}))) && (split(/ +/, hComponent->{"Languages"}) != ()))
|
||
|
{
|
||
|
PrintL("\nLanguage ".$sBVTLanguage." not supported for component ".hComponent->{"Name"}." skipping BVT\n\n", PL_VERBOSE);
|
||
|
return(1); # do not error
|
||
|
}
|
||
|
if (!$bLocal && !$bNoCopy && ($sDropDir ne "") && !IsDirectory($sDropDir))
|
||
|
{
|
||
|
PrintL(hComponent->{"Name"}." ".$sBVTLanguage." ".$sBVTType." build #".$sBVTBuildNumber
|
||
|
." does not exist on server, cannot process BVT\n", PL_BIGWARNING);
|
||
|
PrintMsgBlock($sDropDir." does not exist\n");
|
||
|
return(1); # new behavior (06/99) -> don't fail the bvt if not exist (sometimes we don't build on purpose)
|
||
|
}
|
||
|
|
||
|
PrintL("\n".hComponent->{"Name"}." BVT (".$sBVTLanguage." ".$sBVTType.", Build #".$sBVTBuildNumber.
|
||
|
($bLocal ? " *LOCAL*" : "").")\n", PL_SUBHEADER);
|
||
|
PrintL(("-" x 80)."\n\n", PL_SUBHEADER);
|
||
|
|
||
|
BEGIN_DHTML_NODE("(click to expand)");
|
||
|
|
||
|
$bNothingDone = 0; # (we're starting to do stuff)
|
||
|
if ($bcStatus & BC_NOTHINGDONE)
|
||
|
{
|
||
|
$bcStatus -= BC_NOTHINGDONE;
|
||
|
}
|
||
|
|
||
|
# ************************* COPY PHASE *************************
|
||
|
|
||
|
# build a list of our current component's needed files
|
||
|
|
||
|
my($rc) = 1;
|
||
|
|
||
|
if ($bLocal && !$bNoCopy)
|
||
|
{
|
||
|
foreach $file (split(/ +/, hComponent->{"LocalInputFiles"}))
|
||
|
{
|
||
|
push(@lNeededFiles, $PROJROOT."\\".$file);
|
||
|
}
|
||
|
foreach $file (split(/ +/, hComponent->{"SLMInputFiles"}))
|
||
|
{
|
||
|
push(@lNeededFiles, $PROJROOT."\\".$file);
|
||
|
}
|
||
|
foreach $file (split(/ +/, hComponent->{"ExternalFiles"}))
|
||
|
{
|
||
|
push(@lNeededFiles, $file);
|
||
|
}
|
||
|
foreach $file (@lNeededFiles)
|
||
|
{
|
||
|
# if the file is already there, use the existing one; if not, copy and remember it
|
||
|
# so that we can delete it later
|
||
|
if (!-e RemovePath($file))
|
||
|
{
|
||
|
$rc = EchoedCopy($file) && $rc;
|
||
|
SetReadOnly(RemovePath($file), 0);
|
||
|
push(@lCopiedFiles, $file);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
PrintL("Using local copy of ".RemovePath($file)." (delete to run clean BVT)\n", PL_WARNING);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
elsif (!$bNoCopy)
|
||
|
{
|
||
|
foreach $file (split(/ +/, hComponent->{"RemoteInputFiles"}))
|
||
|
{
|
||
|
push(@lNeededFiles, hComponent->{"RemoteSrcDir"}."\\".$file);
|
||
|
}
|
||
|
foreach $file (split(/ +/, hComponent->{"SLMInputFiles"}))
|
||
|
{
|
||
|
if (-e $sArchivedSrcDir."\\".$sBVTBuildNumber."\\".$file)
|
||
|
{
|
||
|
push(@lNeededFiles, $sArchivedSrcDir."\\".$sBVTBuildNumber."\\".$file);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
push(@lNeededFiles, $sSLMServerRoot."\\".$file);
|
||
|
}
|
||
|
}
|
||
|
foreach $file (split(/ +/, hComponent->{"ExternalFiles"}))
|
||
|
{
|
||
|
push(@lNeededFiles, $file);
|
||
|
}
|
||
|
foreach $file (@lNeededFiles)
|
||
|
{
|
||
|
if (!-e RemovePath($file))
|
||
|
{
|
||
|
$rc = EchoedCopy($file) && $rc;
|
||
|
SetReadOnly(RemovePath($file), 0);
|
||
|
push(@lCopiedFiles, $file);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
PrintL("Warning: Using local copy of ".$file." (delete to run clean BVT)\n", PL_WARNING);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
# ************************* BVT-EXECUTION PHASE *************************
|
||
|
|
||
|
my $startTime = time();
|
||
|
if ($rc)
|
||
|
{
|
||
|
foreach $component (keys(%$hConfig))
|
||
|
{
|
||
|
if ($sBVTComponent eq $component)
|
||
|
{
|
||
|
local(*BVTFunction) = hComponent->{"Function"};
|
||
|
$rc = BVTFunction();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
my($bvtTime) = FmtDeltaTime(time() - $startTime);
|
||
|
|
||
|
if (!$rc)
|
||
|
{
|
||
|
++$nBVTFailureCount;
|
||
|
}
|
||
|
|
||
|
# backup failed BVT files in subdir for future reference
|
||
|
if (!$rc && !$bNoCopy)
|
||
|
{
|
||
|
PrintL("\n".'BVT failed, saving BVT failure src files in '.cwd().'\BVTFail'.$nBVTFailureCount."\n\n", PL_ERROR);
|
||
|
Delnode('BVTFail'.$nBVTFailureCount);
|
||
|
EchoedMkdir('BVTFail'.$nBVTFailureCount);
|
||
|
foreach $file (@lNeededFiles)
|
||
|
{
|
||
|
if (-e RemovePath($file))
|
||
|
{
|
||
|
EchoedCopy(RemovePath($file), 'BVTFail'.$nBVTFailureCount."\\".RemovePath($file));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
PrintL("Warning: ".RemovePath($file)." in \@lNeededFiles does not exist, clean up your list\n",
|
||
|
PL_WARNING | PL_VERBOSE);
|
||
|
}
|
||
|
}
|
||
|
foreach $file (split(/ +/, hComponent->{"OutputFiles"}))
|
||
|
{
|
||
|
if (-e RemovePath($file))
|
||
|
{
|
||
|
EchoedCopy(RemovePath($file), 'BVTFail'.$nBVTFailureCount."\\".RemovePath($file));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
PrintL("Warning: ".RemovePath($file)." in \"OutputFiles\" does not exist, clean up your list\n",
|
||
|
PL_WARNING | PL_VERBOSE);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
# ************************* CLEAN-UP PHASE *************************
|
||
|
|
||
|
if (!$bNoCopy)
|
||
|
{
|
||
|
foreach $file (@lCopiedFiles)
|
||
|
{
|
||
|
if (RemovePath($file) !~ /(\\|\/)/i) # just to make sure (we don't want to delete anything off the servers)
|
||
|
{
|
||
|
EchoedUnlink(RemovePath($file));
|
||
|
}
|
||
|
}
|
||
|
foreach $file (split(/ +/, hComponent->{"OutputFiles"}))
|
||
|
{
|
||
|
EchoedUnlink($file);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
END_DHTML_NODE();
|
||
|
PrintL("\n");
|
||
|
|
||
|
if ($rc)
|
||
|
{
|
||
|
PrintLTip($sBVTLanguage." ".hComponent->{"Name"}." ".ucfirst(lc($sBVTType))." BVT Succeeded\n\n",
|
||
|
"BVT Execution Time: ".$bvtTime,
|
||
|
PL_MSG | PL_GREEN | PL_FLUSH);
|
||
|
if ($ERROR ne "")
|
||
|
{
|
||
|
PrintMsgBlock($ERROR);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
PrintLTip($sBVTLanguage." ".hComponent->{"Name"}." ".ucfirst(lc($sBVTType))." BVT FAILED\n\n",
|
||
|
"BVT Execution Time: ".$bvtTime,
|
||
|
PL_BIGERROR | PL_FLUSH);
|
||
|
PrintMsgBlock($ERROR);
|
||
|
}
|
||
|
|
||
|
return($rc);
|
||
|
}
|
||
|
|
||
|
$__NLGBVTPM = 1;
|
||
|
1;
|
||
|
|