diff --git a/beeminder.pl b/beeminder.pl index ad0db3f..a294c86 100755 --- a/beeminder.pl +++ b/beeminder.pl @@ -46,10 +46,11 @@ # need to: 1. it doesn't exist or is empty; 2. any beeminder IDs are missing # from the cache file; 3. there are multiple datapoints for the same day. $bflag = (!-s $beef); -my $bf1 = 0; my $bf2 = 0; my $bf3 = 0; my $bf4 = 0; # why bflag? +my $bf1 = 0; my $bf2 = 0; my $bf3 = 0; my $bf4 = 0; my $bf5 = 0; # why bflag? $bf1 = 1 if $bflag; undef %remember; # remember which dates we've already seen in the cache file -if(open(B, "<$beef")) { +if($remote_id ne "") { $bflag = 1; $bf1 = 0; $bf5 = 1 } +elsif(open(B, "<$beef")) { while(my $l = ) { my($y,$m,$d,$v,$p,$c,$b) = ($l =~ / (\d+)\s+ # year @@ -102,6 +103,8 @@ print "Cache file has duplicate Bmndr IDs; recreating... "; } elsif($bf4) { print "Couldn't read cache file; recreating... "; + } elsif($bf5) { + print "Using remote sync, skipping cache file... "; } else { # this case is impossible print "Recreating Beeminder cache ($tmp)[$bf1$bf2$bf3$bf4]... "; } @@ -182,6 +185,7 @@ my $minus = 0; # total number of pings decreased from what's on beeminder my $plus = 0; # total number of pings increased from what's on beeminder my $ii = 0; +my $delall = 0; for(my $t = daysnap($start)-86400; $t <= daysnap($end)+86400; $t += 86400) { my($y,$m,$d) = dt($t); my $ts = "$y-$m-$d"; @@ -200,9 +204,19 @@ $bh{$ts} = beemcreate($usr,$slug,$t, $p1*$ping, splur($p1,"ping").": ".$s1); #print "Created: $y $m $d ",$p1*$ping," \"$p1 pings: $s1\"\n"; } elsif($p0 > 0 && $p1 <= 0) { # on beeminder but not in tagtime log: DELETE - $ndel++; - $minus += $p0; - beemdelete($usr, $slug, $b); + my $resp = 'y'; + unless($delall) { + print "Beeminder point not found in tagtime log! Delete? [y/N]"; + $resp = ; + } + if($resp =~ /^y/i or $delall) { + $ndel++; + $minus += $p0; + beemdelete($usr, $slug, $b); + if($resp =~ /^Y/) { $delall = 1; } + } else { + print "Not deleting! Please fix your logs and run beeminder.pl manually!" + } #print "Deleted: $y $m $d ",$p0*$ping," \"$p0 pings: $s0 [bID:$b]\"\n"; } elsif($p0 != $p1 || $s0 ne $s1) { # bmndr & tagtime log differ: UPDATE $nchg++; diff --git a/get_latest.awk b/get_latest.awk new file mode 100644 index 0000000..d43487c --- /dev/null +++ b/get_latest.awk @@ -0,0 +1 @@ +{if($1 > max) { max = $1; latest = FILENAME}} END { print latest } diff --git a/launch.pl b/launch.pl index 661a693..70c51e9 100755 --- a/launch.pl +++ b/launch.pl @@ -3,10 +3,18 @@ # and/or launch ping.pl for the current ping. # This should be called by the daemon (tagtimed.pl) every time a ping is due. -$launchTime = time(); - require "$ENV{HOME}/.tagtimerc"; require "${path}util.pl"; +require "${path}merge.pl"; + +# Generate derived settings used only in this file +$remote_server = "$remote_user\@$remote_host"; +$remote_log = "$remote_server:$remote_path"; +$remote_sshid = $remote_key eq "" ? "" : "-i $remote_key"; +$scp_cmd = "scp $remote_sshid"; +$ssh_cmd = "ssh $remote_sshid"; + +$launchTime = mytime(); my $args = join(' ', @ARGV); # supported arguments: test, quiet my $test = ($args =~ /\btest\b/); @@ -17,29 +25,24 @@ exit(0); } +if(!lockn()) { + debug("Can't get lock. Exiting."); + exit(1); +} # Don't wait if we can't get the lock. + # figure out the next ping after the last one that's in the log file -if(-e $logf) { - $lll = `tail -1 $logf`; # last log line - $lll =~ /^\s*(\d+)/; # parse out the timestamp for the last line, which better - $lstping = $1; # be equal to nextping@prevping of itself. - $tmp = nextping(prevping($lstping)); # NB: must call prevping before nextping - if($lstping == $tmp) { - $nxtping = nextping($lstping); - } else { - print "TagTime log file ($logf) has bad last line:\n$lll"; - $nxtping = prevping($launchTime); - } -} else { - $nxtping = prevping($launchTime); +$nxtping = parseping($logf); + +if($remote_id ne "" && $nxtping < $launchTime) { + # If we have a gap, first try to fill in with stuff from the most recent remote log + fill_remote(); } -if(!lockn()) { - print "Can't get lock. Exiting.\n" unless $quiet; - exit(1); -} # Don't wait if we can't get the lock. +$nxtping = parseping($logf); my $editorFlag = 0; +debug("Filling in RETRO pings ($launchTime <=> $nxtping)"); # First, if we missed any pings by more than $retrothresh seconds for no # apparent reason, then assume the computer was off and auto-log them. while($nxtping < $launchTime-$retrothresh) { @@ -50,14 +53,49 @@ # Next, ping for any pings in the last retrothresh seconds. do { - while($nxtping <= time()) { - if($nxtping < time()-$retrothresh) { + while($nxtping <= mytime()) { + if($nxtping < mytime()-$retrothresh) { slog(annotime("$nxtping afk RETRO", $nxtping)."\n"); $editorFlag = 1; } else { launch($nxtping); # this shouldn't complete till you answer. } my($ts,$ln) = lastln(); + + debug("Processing ping response ($ts <=> $nxtping, ef=$editorFlag)"); + + # First, check to see if we have remote pings to fill in, if this computer + # was just sitting with a ping window up while they were being answered elsewhere + if($ts != $nxtping) { + my ($rts,$rln) = remoteln(); + if ($rts > $ts) { + debug("$rts > $ts, filling from remote"); + + $verify = nextping(prevping($ts)); # NB: must call prevping before nextping + if($ts == $verify) { + fill_remote(); + } else { + print "Local file has a bad last line:\n$ln"; + $nxtping = prevping($launchTime); + } + # re-read + ($ts,$ln) = lastln(); + + $verify = nextping(prevping($ts)); + if($ts == $verify) { + debug("New last timestamp: $ts"); + $nxtping = $ts + } else { + print "Remote file has a bad last line:\n$ln"; + $nxtping = prevping($launchTime); + } + } else { + debug("$rts <= $ts, nothing to fill from remote"); + } + } + + debug("Checked from remote ($ts <=> $nxtping, ef=$editorFlag)"); + if($ts != $nxtping) { # in case, eg, we closed the window w/o answering. # suppose there's a ping window waiting (call it ping 1), and while it's # sitting there unanswered another ping (ping 2) pings. then you kill @@ -65,7 +103,7 @@ # ping but there will be nothing in the log yet for ping 2. perhaps # that's ok, just thinking out loud here... slog(annotime( - "$nxtping err [missed ping from ".ss(time()-$nxtping)." ago]", + "$nxtping err [missed ping from ".ss(mytime()-$nxtping)." ago]", $nxtping)."\n"); editor($logf,"TagTime Log Editor (unanswered pings logged as \"err\")"); $editorFlag = 0; @@ -75,6 +113,8 @@ $editorFlag = 1; } + debug("Generated err pings ($ts <=> $nxtping, ef=$editorFlag)"); + $lstping = $nxtping; $nxtping = nextping($nxtping); # Here's where we would add an artificial gap of $nxtping-$lstping. } @@ -85,20 +125,62 @@ # that's why we have the outer do-while loop here, to start over if # there are new pings in the past after we finish editing. } -} while($nxtping <= time()); +} while($nxtping <= mytime()); +if($remote_id ne "") { + debug("Backing up log to remote server..."); + system("$scp_cmd -C $logf $remote_log$usr.$remote_id.log"); + debug("Making commit if remote is a git repo..."); + system("$ssh_cmd $remote_server 'cd $remote_path; [ -d .git ] && git commit --author=\"Tagtime \" -am \"Backup from $remote_id\"'"); +} unlock(); -# Returns the last line in the log but as a 2-element array +# Parses a log line as a 2-element array # consisting of timestamp and rest of the line. +sub parseln { + my ($x) = @_; + $x =~ /^\s*(\d+)\s*(.*)$/; + return ($1,$2); +} +# Returns the last line in the log as a 2-elm array sub lastln { my $x; open(L, $logf) or die "ERROR-lastln: Can't open log: $!"; $x = $_ while(); close(L); - $x =~ /^\s*(\d+)\s*(.*)$/; - return ($1,$2); + return parseln($x); +} + +# Returns the last line in the remote log as a 2-elm array +sub remoteln { + # If we have a gap, first try to fill in with stuff from the most recent remote log + $remote_line = `$ssh_cmd $remote_server 'cd $remote_path && tail -n1 -q cincodenada.*.log | sort | tail -n1'`; + return parseln($remote_line); +} + +sub fill_remote { + debug("Downloading remote files..."); + system("$scp_cmd $remote_log$usr.*.log ."); + # Remove our log, we are source of truth for it + # Otherwise we overwrite our own edits, bleh + unlink "$usr.$remote_id.log"; + + @mergefiles = glob("$path$usr.*.log"); + + debug("Merging pings from remote files..."); + if(-e $logf) { + push(@mergefiles, $logf); + system("cp $logf $logf.backup"); + } + open NEWLOG, ">", "$logf.merge"; + print(@mergefiles); + if(merge(NEWLOG, 0, @mergefiles) == 0) { + system("mv $logf.merge $logf"); + debug("Merge successful"); + } else { + debug("Merge errors! Leaving empty merge file in place to signal ping") + } } # Launch the tagtime pinger for the given time (in unix time). @@ -106,7 +188,7 @@ sub launch { my($t) = @_; my($sec,$min,$hour) = localtime($t); $sec = dd($sec); $min = dd($min); $hour = dd($hour); - #$ENV{DISPLAY} = ":0.0"; # have to set this explicitly if invoked by cron. + $ENV{DISPLAY} ||= ":0.0"; # have to set this explicitly if invoked by cron. if(!$quiet) { if(!defined($playsound)) { print STDERR "\a"; } else { system("$playsound") == 0 or print "SYSERR: $playsound\n"; } @@ -131,6 +213,28 @@ sub editor { } } +sub parseping { + local $nxtping, $lastping; + local ($logf) = @_; + print("Launch time: $launchTime\n"); + # figure out the next ping after the last one that's in the log file + if(-e $logf) { + $lll = `tail -1 $logf`; # last log line + $lll =~ /^\s*(\d+)/; # parse out the timestamp for the last line, which better + $lstping = $1; # be equal to nextping@prevping of itself. + $tmp = nextping(prevping($lstping)); # NB: must call prevping before nextping + if($lstping == $tmp) { + $nxtping = nextping($lstping); + } else { + print "TagTime log file ($logf) has bad last line:\n$lll"; + $nxtping = prevping($launchTime); + } + } else { + $nxtping = prevping($launchTime); + } + + return $nxtping; +} # SCHDEL (SCHEDULED FOR DELETION): (discussion and code for artificial gaps) # It can happen that 2 pings can both occur since we last checked (a minute @@ -146,7 +250,7 @@ sub editor { # with zero gap. # if another ping is overdue, mind the gap! (ie delay the 2nd ping so as to # maintain the original gap betw them (but not more than retrothresh)): - #my $now = time(); + #my $now = mytime(); #my $eaten = $now - $prompt; # subtract amount of time eaten up # # answering last ping #if ($nxtping<$now && $nxtping>=$now-$retrothresh) { diff --git a/merge.pl b/merge.pl index 07d57b5..b3b4920 100755 --- a/merge.pl +++ b/merge.pl @@ -2,20 +2,20 @@ # Merge the tagtime logs given on the command line; output to stdout. # If only one log file is given this just fills in any missing pings autotagged # with MISSING and autotags pings that shouldn't be there with UNSCHED. -# NB: The rest of this is not fully implemented yet! -# Currently just concatenates the tags from each log file. -# Eventual spec follows, and in the meantime you can use it with a single +# NB: The rest of this is not fully implemented yet! +# Currently just concatenates the tags from each log file. +# Eventual spec follows, and in the meantime you can use it with a single # log file to just sanity check it... # If multiple log files are given this will properly merge them, like if you # use tagtime on multiple computers. -# Any ping that, according to the ping schedule, is missing from all the given -# logs will be added with the autotag MISSING and any pings present in any of +# Any ping that, according to the ping schedule, is missing from all the given +# logs will be added with the autotag MISSING and any pings present in any of # the logs that shouldn't be there (again, according to the ping schedule) will # have the autotag UNSCHED appended. -# For each outputted ping with timestamp t, include the union of the tags with +# For each outputted ping with timestamp t, include the union of the tags with # timestamp t in all the given log files, ignoring log files that are tagged # only with autotags at time t. Unless *all* the log files are tagged only with -# autotags at time t, in wich case go ahead and do the union like normal. +# autotags at time t, in wich case go ahead and do the union like normal. # Autotags are {MISSING, UNSCHED, RETRO, afk, off, err}. # The earliest timestamp outputted is the earliest timestamp in all the logs. # The latest timestamp outputted is the max of now and the latest in the logs. @@ -26,78 +26,119 @@ # missflag = true # let @p = {}, a list of ping responses for each log file # for each log file l: -# if tags{l+t} not empty: missflag = false +# if tags{l+t} not empty: missflag = false # push(@p, tags{l+t}) # if sch{t} and missflag: push(@p, "MISSING") # if not sch{t}: push(@p, "UNSCHED") # print t, join('+', @p) BEGIN { require "$ENV{HOME}/.tagtimerc"; } -require "util.pl"; +require "${path}util.pl"; +use List::MoreUtils qw(uniq); -die "USAGE: $0 logfile+\n" if @ARGV < 1; - -my $e = 0; # number of lines with parse errors -my $errstr = ""; # concatenation of bad lines from log files -my $earliest = -1; # earliest timestamp in all the log files -my $latest = 0; # latest timestamp in all the log files -my %th; # maps logfile+timestamp to tags for that log for that ping -my %alltimes; # maps all timestamps to 1 -for my $logfile (@ARGV) { - open(LOG, $logfile) or die; - $prevts = 0; # remember the previous timestamp - while($line = ) { - if(!parsable($line)) { - $e++; - $errstr .= $line; - next; - } - my @tags = split(/\s+/, $line); - my $ts = shift(@tags); - if($ts <= $prevts) { - $e++; - $errstr .= "NON-MONOTONE:\n$line"; - next; - } - $prevts = $ts; - if($ts < $earliest || $earliest == -1) { $earliest = $ts; } - if($ts > $latest) { $latest = $ts; } - $line =~ s/^\d+\s+//; - chomp($line); - $th{$logfile.$ts} = $line; - $alltimes{$ts} = 1; - } - close(LOG); -} +sub merge { + my $fh = shift; + my $fill_now = shift; + my $e = 0; # number of lines with parse errors + my $errstr = ""; # concatenation of bad lines from log files + my %errfiles; # map of files with errors + my $earliest = -1; # earliest timestamp in all the log files + my $latest = 0; # latest timestamp in all the log files + my %th; # maps logfile+timestamp to tags for that log for that ping + my %alltimes; # maps all timestamps to 1 + my @files = @_; + for my $logfile (@files) { + open(LOG, $logfile) or die "Couldn't open $logfile"; + $prevts = 0; # remember the previous timestamp + while($line = ) { + if(!parsable($line)) { + $e++; + $errstr .= $line; + $errfiles{$logfile} = $e; + next; + } + my @tags = split(/\s+/, $line); + my $ts = shift(@tags); + if($ts <= $prevts) { + $e++; + $errstr .= "NON-MONOTONE in $logfile:\n$line"; + $errfiles{$logfile} = $e; + next; + } + $prevts = $ts; + if($ts < $earliest || $earliest == -1) { $earliest = $ts; } + if($ts > $latest) { $latest = $ts; } + $line =~ s/^\d+\s+//; + chomp($line); + $th{$logfile.$ts} = $line; + $alltimes{$ts} = 1; + } + close(LOG); + } -if($e>0) { - print "Errors in log file(s): $e. ", - "They have to be fixed before this script can run:\n"; - print "\n$errstr"; - exit(1); -} + if($e>0) { + my $files = join ",", keys %errfiles; + print STDERR "Errors in log file(s): $files ", + "Please fix errors and then re-run merge.pl manually:\n"; + print STDERR "\n$errstr"; + return 1; + } -my $now = time(); -if($now > $latest) { $latest = $now; } -my %sch; # maps timestamps to whether they are a scheduled pings -my $i = prevping($earliest); -$i = nextping($i); -while($i <= $latest) { - $sch{$i} = 1; - $alltimes{$i} = 1; + if($fill_now) { + my $now = time(); + if($now > $latest) { $latest = $now; } + } + my %sch; # maps timestamps to whether they are a scheduled pings + my $i = prevping($earliest); $i = nextping($i); -} + while($i <= $latest) { + $sch{$i} = 1; + $alltimes{$i} = 1; + $i = nextping($i); + } -for my $t (sort(keys(%alltimes))) { - my $missflag = 1; - my @p = (); - for my $l (@ARGV) { - if(defined($th{$l.$t})) { - $missflag = 0; - push(@p, $th{$l.$t}); + # We ignore these entries, using them only if we have nothing else + my %ignore = map { $_ => 1 } ('afk off RETRO', 'afk RETRO', 'err'); + for my $t (sort(keys(%alltimes))) { + my $missflag = 1; + my @p = (); + my @backup; + for my $l (@files) { + if(defined($th{$l.$t})) { + $missflag = 0; + + # Pull out just the tags + my $line = $th{$l.$t}; + $line =~ s/\s+\[.*?\]$//; + my @tags = split(/\s+/, $line); + + if(exists($ignore{$line})) { + # Ignore ignorables, but stash the longest one, + # so we have something in case all entries are ignorable + if($#tags >= $#backup) { @backup = @tags; } + } else { + # Otherwise add our tags to the list + push(@p, @tags); + } + } } + if($sch{$t} && $missflag) { push(@p, 'MISSING'); } + if(!$sch{$t}) { push(@p, 'UNSCHED'); } + + # If we have tags get the unique set, otherwise use the line we stashed + my @combined = @p ? uniq @p : @backup; + + print $fh $t, ' ', annotime(join(' ', @combined), $t, 72), "\n"; } - if($sch{$t} && $missflag) { push(@p, annotime('MISSING', $t, 33)); } - if(!$sch{$t}) { push(@p, 'UNSCHED'); } - print $t, " ", join(' + ', @p), "\n"; + + return 0; } + +sub run { + die "USAGE: $0 logfile+\n" if @ARGV < 1; + exit(merge(STDOUT, 1, @ARGV)); +} + +run unless caller; + +1; diff --git a/ping.pl b/ping.pl index 6ecebfc..7744320 100755 --- a/ping.pl +++ b/ping.pl @@ -12,12 +12,12 @@ Term::ANSIColor->import(':constants'); }; -my $pingTime = time(); -my $autotags = ""; - require "$ENV{HOME}/.tagtimerc"; require "${path}util.pl"; +my $pingTime = mytime(); +my $autotags = ""; + my $tskf = "$path$usr.tsk"; my $eflag = 0; # if any problems then prompt before exiting @@ -27,7 +27,7 @@ $t = shift; if(!defined($t)) { $autotags .= " UNSCHED"; - $t = time(); + $t = mytime(); } # Can't lock the same lockfile here since launch.pl will have the lock! @@ -35,6 +35,26 @@ # instances are invoked, but launch.pl will only launch one at a time. #lockb(); # wait till we can get the lock. +# Warn about merge errors if there's a merge file remaining +if(-e "$logf.merge") { + print divider(""), "\n"; + print divider(" WARNING "x8), "\n"; + print divider(""), "\n"; + print "Failed merge to resolve!\n"; + print < $usr.log.merge + +When you are satisfied, replace your logfile with the merged file, removing the +merge file. Your remote logs will not get synced until you do so! +EOS + print divider(""), "\n\n"; +} + if($pingTime-$t > 9) { print divider(""), "\n"; print divider(" WARNING "x8), "\n"; diff --git a/settings.pl.template b/settings.pl.template index 4c4a3df..0a88125 100644 --- a/settings.pl.template +++ b/settings.pl.template @@ -7,6 +7,14 @@ $path = "__PATH__"; # CHANGEME to your path to tagtime if($path !~ /\/$/) { $path.="/"; } $logf = "$path$usr.log"; # log file for pings +$remote_key = "id_tagtime"; # CHANGEME to the file contaning your tagtime key (if different) +$remote_user = "tagtime"; # CHANGEME to your backup user (if different) +$remote_host = "__REMHOST__"; # CHANGEME to your backup server +$remote_path = "__REMPATH__"; # CHANGEME to the path on your backup server + if($remote_path !~ /\/$/) { $remote_path.="/"; } +$remote_id = ""; # CHANGEME to a unique tag for this machine on the backup server + # Leaving this blank will disable backup functionality + # If you're using windows, you'll need cygwin and to set this flag to 1: $cygwin = __CYGWIN__; # CHANGEME to 1 if you're using windows/cygwin. diff --git a/tagtimed.service b/tagtimed.service new file mode 100644 index 0000000..9220a73 --- /dev/null +++ b/tagtimed.service @@ -0,0 +1,13 @@ +[Unit] +Description=TagTime +Documentation= + +[Service] +Type=simple +ExecStart=/home/joel/bin/TagTime/tagtimed.pl +WorkingDirectory=/home/joel/bin/TagTime/ +Restart=always +RestartSec=10 + +[Install] +WantedBy=default.target diff --git a/test/.tagtimerc b/test/.tagtimerc new file mode 100644 index 0000000..d4a10e5 --- /dev/null +++ b/test/.tagtimerc @@ -0,0 +1,50 @@ +use File::Basename; + +$usr = "tagtime_test"; +$path = "$ENV{HOME}/../"; +$logf = "$path/test/logs/$ENV{LOGFILE}.log"; # log file for pings + +$remote_key = "id_tagtime"; # CHANGEME to the file contaning your tagtime key (if different) +$remote_user = "tagtime"; # CHANGEME to your backup user (if different) +$remote_host = "fakehost"; # CHANGEME to your backup server +$remote_path = "fakepath"; # CHANGEME to the path on your backup server + if($remote_path !~ /\/$/) { $remote_path.="/"; } +$remote_id = $ENV{REMOTEID}; # CHANGEME to a unique tag for this machine on the backup server + # Leaving this blank will disable backup functionality + +$remote_server = "$remote_user\@$remote_host"; +$remote_log = "$remote_server:$remote_path"; +$remote_sshid = ""; + if ($remote_key ne "") { $remote_sshid = "-i $remote_key"; } + +$scp_cmd = "perl ${path}test/fakescp.pl"; +$ssh_cmd = "perl ${path}test/fakessh.pl"; + +# If you're using windows, you'll need cygwin and to set this flag to 1: +$cygwin = 0; # CHANGEME to 1 if you're using windows/cygwin. + +$ED = "perl ${path}test/fakeedit.pl"; # CHANGEME if you don't like vi (eg: /usr/bin/pico) +$XT = "perl ${path}test/faketerm.pl"; # CHANGEME to your path to xterm + +# Get your personal Beeminder auth token (after signing in) from +# https://www.beeminder.com/api/v1/auth_token.json +$beemauth = "abc123"; # CHANGEME to your personal beeminder auth token + +%beeminder = (); + +$retrothresh = 60; + +$gap = 45*60; # Average number of seconds between pings (eg, 60*60 = 1 hour). + +$seed = 666; # For pings not in sync with others, change this (NB: > 0). + +$linelen = 79; # Try to keep log lines at most this long. + +$catchup = 0; # Whether it beeps for old pings, ie, should it beep a bunch + # of times in a row when the computer wakes from sleep. + +$enforcenums = 0; # Whether it forces you to include a number in your + # ping response (include tag non or nonXX where XX is day + # of month to override). This is for task editor integration. + +1; # When requiring a library in perl it has to return 1. diff --git a/test/fakescp.pl b/test/fakescp.pl new file mode 100644 index 0000000..fac2bab --- /dev/null +++ b/test/fakescp.pl @@ -0,0 +1,11 @@ +#!/usr/bin/env perl +my @argflags = []; +while(substr($ARGV[0], 0, 1) eq '-') { + $flag = shift @ARGV; + if(grep(/^$flag$/, @argflags)) { + $val = shift @ARGV; + } + print("Ignored flag $flag $val\n"); +} +my ($src, $dest) = @ARGV; +print "Would have copied from $src to $dest\n"; diff --git a/test/fakessh.pl b/test/fakessh.pl new file mode 100644 index 0000000..fa5693e --- /dev/null +++ b/test/fakessh.pl @@ -0,0 +1,3 @@ +#!/usr/bin/env perl +my ($server, $cmd) = @ARGV; +print "Would have run '$cmd' on $server\n"; diff --git a/test/launch.pl b/test/launch.pl new file mode 100644 index 0000000..235c0ee --- /dev/null +++ b/test/launch.pl @@ -0,0 +1,73 @@ +#!/usr/bin/env perl +use File::Basename; + +my $assertions_passed = 0; + +END { + print("$assertions_passed assertions passed!\n"); +} + +sub assert_numeq { + my ($expected, $actual) = @_; + assert($expected == $actual, "Expected $expected, got $actual"); +} + +sub assert { + my ($condition, $msg) = @_; + if($condition) { + $assertions_passed++; + return; + } + if (!$msg) { + my ($pkg, $file, $line) = caller(0); + open my $fh, "<", $file; + my @lines = <$fh>; + close $fh; + $msg = "$file:$line: " . $lines[$line - 1]; + } + die "Assertion failed: $msg"; +} + +# Override home to be our test dir +$ENV{HOME} = "./" . dirname(__FILE__); +eval { + require "$ENV{HOME}/.tagtimerc"; +}; +if ($@) { + die "$0: $ENV{HOME}/.tagtimerc cannot be loaded ($!). Do you need to run install.py?\n" +} + +require "${path}util.pl"; + +my $baseTime = 1592693841; # Birth of this test suite + +my $lstping = prevping($baseTime); +my $nxtping = nextping($lstping); + +assert_numeq(1592691017, $lstping); +assert_numeq(1592696838, $nxtping); + +################## +# Test initializing a new log +################## + +$newlog = "$path/test/logs/nonexistent.log"; +unlink $newlog; +$ENV{LOGFILE} = "nonexistent"; + +# This is finicky because we do a lot of time checks +# One check for $launchTime at the beginning, then loops +# Each inner loop we check: +# - beginning of loop +# - retrothresh +# - err (if we closed launch window) +# Plus one for outer loop (after filling in any RETRO) +$ENV{FAKETIMES} = join(",", ( + $lstping + 10, + $lstping + 11, # first $nxtping check + $lstping + 12, # retrothresh + $lstping + 13, # pingtime +)); + +$cmd = "${path}launch.pl"; # Catch up on any old pings. +system($cmd) == 0 or print "SYSERR: $cmd\n"; diff --git a/test/logs/empty b/test/logs/empty new file mode 100644 index 0000000..e69de29 diff --git a/util.pl b/util.pl index d6d0604..4917f1c 100644 --- a/util.pl +++ b/util.pl @@ -237,6 +237,13 @@ sub slog { close(F); } +sub debug { + my $line = shift; + unless($quiet) { + print "$line\n"; $| = 1; + } +} + # double-digit: takes number from 0-99, returns 2-char string eg "03" or "42". sub dd { my($n) = @_; return padl($n, "0", 2); } # simpler but less general version: return ($n<=9 && $n>=0 ? "0".$n : $n) @@ -359,6 +366,18 @@ sub padr { return timelocal($second,$minute,$hour,$day,$month-1,$year); } +# Testing mocks +sub mytime() { + if(exists($ENV{FAKETIMES})) { + my @faketimes = split(",", $ENV{FAKETIMES}); + $next_time = shift @faketimes; + $ENV{FAKETIMES} = join(",", @faketimes); + print "Dispensing time $next_time\n"; + return shift @faketimes; + } + return time(); +} + 1; # perl wants this for libraries imported with 'require'.