| 46 | } |
| 47 | |
| 48 | int main() { |
| 49 | // epoch 1700000000 == 2023-11-14 22:13:20 UTC (gmtime → deterministic). |
| 50 | const std::string kDay = "2023-11-14"; |
| 51 | |
| 52 | // ---- bash: "#<epoch>" precedes the command; last line has no timestamp. |
| 53 | { |
| 54 | std::string content = |
| 55 | "#1700000000\n" |
| 56 | "ls -la\n" |
| 57 | "#1700000005\n" |
| 58 | "sudo reboot\n" |
| 59 | "echo no-timestamp\n" |
| 60 | "#!/bin/bash\n"; // a '#' line that is NOT a timestamp |
| 61 | auto r = parse_history_bytes(".bash_history", content); |
| 62 | const ShellCmd* ls = find_cmd(r, "ls -la"); |
| 63 | const ShellCmd* rb = find_cmd(r, "sudo reboot"); |
| 64 | const ShellCmd* ne = find_cmd(r, "echo no-timestamp"); |
| 65 | const ShellCmd* sh = find_cmd(r, "#!/bin/bash"); |
| 66 | CHECK(ls && contains(ls->timestamp, kDay), "bash: '#<epoch>' pairs with NEXT command (ls -la)"); |
| 67 | CHECK(ls && ls->source == "bash/.bash_history", "bash: source names shell + file"); |
| 68 | CHECK(ls && has_prefix(ls->note, "high:"), "bash: on-disk line is high-confidence"); |
| 69 | CHECK(rb && contains(rb->timestamp, kDay), "bash: second '#<epoch>' pairs correctly"); |
| 70 | CHECK(ne && ne->timestamp.empty(), "bash: trailing line without marker has no timestamp"); |
| 71 | CHECK(sh != nullptr, "bash: '#!/bin/bash' kept as a command, not a timestamp"); |
| 72 | } |
| 73 | |
| 74 | // ---- zsh: extended-history ": <ts>:<dur>;cmd" + a plain line. |
| 75 | { |
| 76 | std::string content = |
| 77 | ": 1700000000:0;git status\n" |
| 78 | ": 1700000010:5;make -j8\n" |
| 79 | "plain-zsh-command\n"; |
| 80 | auto r = parse_history_bytes(".zsh_history", content); |
| 81 | const ShellCmd* gs = find_cmd(r, "git status"); |
| 82 | const ShellCmd* mk = find_cmd(r, "make -j8"); |
| 83 | const ShellCmd* pl = find_cmd(r, "plain-zsh-command"); |
| 84 | CHECK(gs && contains(gs->timestamp, kDay), "zsh: extended-history timestamp parsed (git status)"); |
| 85 | CHECK(gs && gs->source == "zsh/.zsh_history", "zsh: source names shell + file"); |
| 86 | CHECK(mk && contains(mk->timestamp, kDay), "zsh: second extended entry (make -j8)"); |
| 87 | CHECK(pl && pl->timestamp.empty(), "zsh: plain line kept without timestamp"); |
| 88 | } |
| 89 | |
| 90 | // ---- fish: YAML "- cmd:" then "when:" (timestamp FOLLOWS the command). |
| 91 | { |
| 92 | std::string content = |
| 93 | "- cmd: echo hello\n" |
| 94 | " when: 1700000000\n" |
| 95 | "- cmd: ls /tmp\n" |
| 96 | " when: 1700000020\n" |
| 97 | " paths:\n" |
| 98 | " - /tmp\n"; |
| 99 | auto r = parse_history_bytes("fish_history", content); |
| 100 | const ShellCmd* eh = find_cmd(r, "echo hello"); |
| 101 | const ShellCmd* lt = find_cmd(r, "ls /tmp"); |
| 102 | CHECK(eh && contains(eh->timestamp, kDay), "fish: 'when:' timestamp pairs with preceding cmd (echo hello)"); |
| 103 | CHECK(eh && eh->source == "fish/fish_history", "fish: source names shell + file"); |
| 104 | CHECK(lt && contains(lt->timestamp, kDay), "fish: second record's 'when:' parsed (ls /tmp)"); |
| 105 | CHECK(lt != nullptr, "fish: 'paths:' block does not swallow the next record"); |
nothing calls this directly
no test coverage detected