github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/web/src/LogStore.test.ts (about)

     1  import { logLinesToString } from "./logs"
     2  import LogStore from "./LogStore"
     3  
     4  describe("LogStore", () => {
     5    function now() {
     6      return new Date().toString()
     7    }
     8  
     9    function newGlobalSegment(text: string): Proto.webviewLogSegment {
    10      return { text: text, time: now() }
    11    }
    12    function newGlobalLevelSegment(
    13      level: string,
    14      text: string
    15    ): Proto.webviewLogSegment {
    16      return { level: level, text: text, time: now() }
    17    }
    18    function newManifestSegment(
    19      name: string,
    20      text: string
    21    ): Proto.webviewLogSegment {
    22      return { spanId: name, text: text, time: now() }
    23    }
    24  
    25    it("handles simple printing", () => {
    26      let logs = new LogStore()
    27      logs.append({
    28        spans: { "": {} },
    29        segments: [newGlobalSegment("foo"), newGlobalSegment("bar")],
    30      })
    31  
    32      expect(logLinesToString(logs.allLog(), true)).toEqual("foobar")
    33    })
    34  
    35    it("handles caching", () => {
    36      let logs = new LogStore()
    37      logs.append({
    38        spans: { "": {} },
    39        segments: [newGlobalSegment("foo")],
    40        fromCheckpoint: 0,
    41        toCheckpoint: 1,
    42      })
    43  
    44      expect(logLinesToString(logs.allLog(), true)).toEqual("foo")
    45      expect(logs.lineCache[0].text).toEqual("foo")
    46      expect(logs.allLog()[0]).toStrictEqual(logs.allLog()[0])
    47  
    48      logs.append({
    49        spans: { "": {} },
    50        segments: [newGlobalSegment("bar")],
    51        fromCheckpoint: 1,
    52        toCheckpoint: 2,
    53      })
    54  
    55      expect(logLinesToString(logs.allLog(), true)).toEqual("foobar")
    56      expect(logs.lineCache[0].text).toEqual("foobar")
    57    })
    58  
    59    it("handles changing levels", () => {
    60      let logs = new LogStore()
    61      logs.append({
    62        spans: { "": {} },
    63        segments: [
    64          newGlobalLevelSegment("INFO", "foo"),
    65          newGlobalLevelSegment("DEBUG", "bar"),
    66          newGlobalLevelSegment("INFO", "baz"),
    67        ],
    68      })
    69  
    70      expect(logLinesToString(logs.allLog(), true)).toEqual("foo\nbar\nbaz")
    71    })
    72  
    73    it("handles prefixes in all logs", () => {
    74      let logs = new LogStore()
    75      logs.append({
    76        spans: { "": {}, fe: { manifestName: "fe" } },
    77        segments: [
    78          newGlobalSegment("line1\n"),
    79          newManifestSegment("fe", "line2\n"),
    80          newGlobalSegment("line3\n"),
    81        ],
    82      })
    83  
    84      expect(logLinesToString(logs.allLog(), true)).toEqual(
    85        "line1\nfe          ┊ line2\nline3"
    86      )
    87    })
    88  
    89    it("handles long-prefixes", () => {
    90      let logs = new LogStore()
    91      logs.append({
    92        spans: {
    93          "": {},
    94          "cockroachdb-frontend": { manifestName: "cockroachdb-frontend" },
    95        },
    96        segments: [
    97          newGlobalSegment("line1\n"),
    98          newManifestSegment("cockroachdb-frontend", "line2\n"),
    99          newGlobalSegment("line3\n"),
   100        ],
   101      })
   102  
   103      expect(logLinesToString(logs.allLog(), true)).toEqual(
   104        "line1\ncockroachdb…┊ line2\nline3"
   105      )
   106    })
   107  
   108    it("handles manifest logs", () => {
   109      let logs = new LogStore()
   110      logs.append({
   111        spans: { "": {}, fe: { manifestName: "fe" } },
   112        segments: [
   113          newGlobalSegment("line1\n"),
   114          newManifestSegment("fe", "line2\n"),
   115          newGlobalSegment("line3\n"),
   116        ],
   117      })
   118  
   119      expect(logLinesToString(logs.manifestLog("fe"), false)).toEqual("line2")
   120    })
   121  
   122    it("handles manifest spans with no segments", () => {
   123      let logs = new LogStore()
   124      logs.append({
   125        spans: {
   126          "": {},
   127          fe: { manifestName: "fe" },
   128          foo: { manifestName: "fe" },
   129        },
   130        segments: [
   131          newGlobalSegment("line1\n"),
   132          newManifestSegment("fe", "line2\n"),
   133          newGlobalSegment("line3\n"),
   134        ],
   135      })
   136  
   137      expect(logLinesToString(logs.manifestLog("fe"), false)).toEqual("line2")
   138    })
   139  
   140    it("handles multi-span manifest logs", () => {
   141      let logs = new LogStore()
   142      logs.append({
   143        spans: {
   144          "pod-a": { manifestName: "fe" },
   145          "pod-b": { manifestName: "fe" },
   146        },
   147        segments: [
   148          { spanId: "pod-a", text: "pod-a: line1\n", time: now() },
   149          { spanId: "pod-b", text: "pod-b: line2\n", time: now() },
   150          { spanId: "pod-a", text: "pod-a: line3\n", time: now() },
   151        ],
   152      })
   153  
   154      expect(logLinesToString(logs.manifestLog("fe"), false)).toEqual(
   155        "pod-a: line1\npod-b: line2\npod-a: line3"
   156      )
   157      expect(logLinesToString(logs.spanLog(["pod-a", "pod-b"]), false)).toEqual(
   158        "pod-a: line1\npod-b: line2\npod-a: line3"
   159      )
   160      expect(logLinesToString(logs.spanLog(["pod-a"]), false)).toEqual(
   161        "pod-a: line1\npod-a: line3"
   162      )
   163      expect(logLinesToString(logs.spanLog(["pod-b"]), false)).toEqual(
   164        "pod-b: line2"
   165      )
   166      expect(logLinesToString(logs.spanLog(["pod-b"]), false)).toEqual(
   167        "pod-b: line2"
   168      )
   169      expect(logLinesToString(logs.spanLog(["pod-c"]), false)).toEqual("")
   170    })
   171  
   172    it("handles incremental logs", () => {
   173      let logs = new LogStore()
   174      logs.append({
   175        spans: { "": {} },
   176        segments: [newGlobalSegment("line1\n"), newGlobalSegment("line2\n")],
   177        fromCheckpoint: 0,
   178        toCheckpoint: 2,
   179      })
   180      logs.append({
   181        spans: { "": {} },
   182        segments: [newGlobalSegment("line3\n"), newGlobalSegment("line4\n")],
   183        fromCheckpoint: 2,
   184        toCheckpoint: 4,
   185      })
   186      logs.append({
   187        spans: { "": {} },
   188        segments: [
   189          newGlobalSegment("line4\n"),
   190          newGlobalSegment("line4\n"),
   191          newGlobalSegment("line5\n"),
   192        ],
   193        fromCheckpoint: 2,
   194        toCheckpoint: 5,
   195      })
   196      expect(logLinesToString(logs.allLog(), true)).toEqual(
   197        "line1\nline2\nline3\nline4\nline5"
   198      )
   199    })
   200  
   201    it("handles progressLogs interleaved", () => {
   202      let logs = new LogStore()
   203      logs.append({
   204        spans: { "": {} },
   205        segments: [
   206          { text: "layer 1: Pending\n", fields: { progressID: "layer 1" } },
   207          { text: "layer 2: Pending\n", fields: { progressID: "layer 2" } },
   208          { text: "layer 3: Pending\n", fields: { progressID: "layer 3" } },
   209        ],
   210        fromCheckpoint: 0,
   211        toCheckpoint: 3,
   212      })
   213  
   214      expect(logLinesToString(logs.allLog(), true)).toEqual(
   215        "layer 1: Pending\nlayer 2: Pending\nlayer 3: Pending"
   216      )
   217  
   218      logs.append({
   219        segments: [
   220          { text: "layer 2: Finished\n", fields: { progressID: "layer 2" } },
   221        ],
   222        fromCheckpoint: 3,
   223        toCheckpoint: 4,
   224      })
   225  
   226      expect(logLinesToString(logs.allLog(), true)).toEqual(
   227        "layer 1: Pending\nlayer 2: Finished\nlayer 3: Pending"
   228      )
   229    })
   230  
   231    it("handles progressLogs adjacent", () => {
   232      let logs = new LogStore()
   233      logs.append({
   234        spans: { "": {} },
   235        segments: [
   236          { text: "layer 1: Pending\n", fields: { progressID: "layer 1" } },
   237        ],
   238        fromCheckpoint: 0,
   239        toCheckpoint: 1,
   240      })
   241  
   242      expect(logLinesToString(logs.allLog(), true)).toEqual("layer 1: Pending")
   243  
   244      logs.append({
   245        segments: [
   246          { text: "layer 1: Finished\n", fields: { progressID: "layer 1" } },
   247        ],
   248        fromCheckpoint: 1,
   249        toCheckpoint: 2,
   250      })
   251  
   252      expect(logLinesToString(logs.allLog(), true)).toEqual("layer 1: Finished")
   253    })
   254  
   255    it("handles last trace", () => {
   256      let logs = new LogStore()
   257      logs.append({
   258        spans: {
   259          "": {},
   260          "build:1": { manifestName: "fe" },
   261          "pod:1": { manifestName: "fe" },
   262          "build:2": { manifestName: "foo" },
   263          "pod:2": { manifestName: "foo" },
   264        },
   265        segments: [
   266          newManifestSegment("build:1", "build 1\n"),
   267          newManifestSegment("pod:1", "pod 1\n"),
   268          newManifestSegment("build:2", "build 2\n"),
   269          newManifestSegment("pod:2", "pod 2\n"),
   270          newManifestSegment("pod:1", "pod 1 line 2\n"),
   271        ],
   272      })
   273  
   274      expect(logLinesToString(logs.traceLog("build:1"), false)).toEqual(
   275        "build 1\npod 1\npod 1 line 2"
   276      )
   277    })
   278  
   279    it("handles trace ends at next build", () => {
   280      let logs = new LogStore()
   281      logs.append({
   282        spans: {
   283          "": {},
   284          "build:1": { manifestName: "fe" },
   285          "pod:1": { manifestName: "fe" },
   286          "build:2": { manifestName: "fe" },
   287          "pod:2": { manifestName: "fe" },
   288        },
   289        segments: [
   290          newManifestSegment("build:1", "build 1\n"),
   291          newManifestSegment("pod:1", "pod 1\n"),
   292          newManifestSegment("build:2", "build 2\n"),
   293          newManifestSegment("pod:2", "pod 2\n"),
   294        ],
   295      })
   296  
   297      expect(logLinesToString(logs.traceLog("build:1"), false)).toEqual(
   298        "build 1\npod 1"
   299      )
   300    })
   301  
   302    it("handles incremental logs", () => {
   303      let logs = new LogStore()
   304      logs.append({
   305        spans: {
   306          "": {},
   307          "build:1": { manifestName: "fe" },
   308        },
   309        segments: [
   310          newManifestSegment("build:1", "build 1\n"),
   311          newManifestSegment("build:1", "build 2\n"),
   312          newManifestSegment("build:1", "build 3\n"),
   313          newGlobalSegment("global line 1\n"),
   314        ],
   315        fromCheckpoint: 0,
   316        toCheckpoint: 4,
   317      })
   318  
   319      let patch = logs.manifestLogPatchSet("fe", 0)
   320      expect(logLinesToString(patch.lines, false)).toEqual(
   321        "build 1\nbuild 2\nbuild 3"
   322      )
   323  
   324      logs.append({
   325        spans: {
   326          "": {},
   327          "build:1": { manifestName: "fe" },
   328        },
   329        segments: [
   330          newGlobalSegment("global line 2\n"),
   331          newManifestSegment("build:1", "build 4\n"),
   332          newManifestSegment("build:1", "build 5\n"),
   333          newManifestSegment("build:1", "build 6\n"),
   334        ],
   335        fromCheckpoint: 4,
   336        toCheckpoint: 8,
   337      })
   338  
   339      let patch3 = logs.manifestLogPatchSet("fe", patch.checkpoint)
   340      expect(logLinesToString(patch3.lines, false)).toEqual(
   341        "build 4\nbuild 5\nbuild 6"
   342      )
   343    })
   344  
   345    it("handles incremental logs two spans", () => {
   346      let logs = new LogStore()
   347      logs.append({
   348        spans: {
   349          "build:1": { manifestName: "fe" },
   350        },
   351        segments: [
   352          newManifestSegment("build:1", "build 1\n"),
   353          newManifestSegment("build:1", "build 2\n"),
   354          newManifestSegment("build:1", "build 3\n"),
   355        ],
   356        fromCheckpoint: 0,
   357        toCheckpoint: 3,
   358      })
   359  
   360      let patch = logs.manifestLogPatchSet("fe", 0)
   361      expect(logLinesToString(patch.lines, false)).toEqual(
   362        "build 1\nbuild 2\nbuild 3"
   363      )
   364  
   365      logs.append({
   366        spans: {
   367          "build:2": { manifestName: "fe" },
   368        },
   369        segments: [
   370          newManifestSegment("build:2", "build 4\n"),
   371          newManifestSegment("build:2", "build 5\n"),
   372          newManifestSegment("build:2", "build 6\n"),
   373        ],
   374        fromCheckpoint: 3,
   375        toCheckpoint: 6,
   376      })
   377  
   378      let patch2 = logs.manifestLogPatchSet("fe", patch.checkpoint)
   379      expect(logLinesToString(patch2.lines, false)).toEqual(
   380        "build 4\nbuild 5\nbuild 6"
   381      )
   382    })
   383  
   384    it("handles incremental logs continuation", () => {
   385      let logs = new LogStore()
   386      logs.append({
   387        spans: {
   388          "": {},
   389          "build:1": { manifestName: "fe" },
   390        },
   391        segments: [
   392          newManifestSegment("build:1", "build 1\n"),
   393          newManifestSegment("build:1", "build 2\n"),
   394          newManifestSegment("build:1", "build ..."),
   395          newGlobalSegment("global line 1\n"),
   396        ],
   397        fromCheckpoint: 0,
   398        toCheckpoint: 4,
   399      })
   400  
   401      let patch = logs.manifestLogPatchSet("fe", 0)
   402      expect(logLinesToString(patch.lines, false)).toEqual(
   403        "build 1\nbuild 2\nbuild ..."
   404      )
   405  
   406      let patch2 = logs.manifestLogPatchSet("fe", patch.checkpoint)
   407      expect(patch2.lines).toEqual([])
   408  
   409      logs.append({
   410        spans: {
   411          "": {},
   412          "build:1": { manifestName: "fe" },
   413        },
   414        segments: [
   415          newGlobalSegment("global line 2\n"),
   416          newManifestSegment("build:1", "... 3\n"),
   417          newManifestSegment("build:1", "build 4\n"),
   418          newManifestSegment("build:1", "build 5\n"),
   419        ],
   420        fromCheckpoint: 4,
   421        toCheckpoint: 8,
   422      })
   423  
   424      let patch3 = logs.manifestLogPatchSet("fe", patch.checkpoint)
   425      expect(logLinesToString(patch3.lines, false)).toEqual(
   426        "build ...... 3\nbuild 4\nbuild 5"
   427      )
   428    })
   429  
   430    it("truncates output for snapshots", () => {
   431      let logs = new LogStore()
   432  
   433      logs.append({
   434        spans: {
   435          "build:1": { manifestName: "fe" },
   436        },
   437        segments: [newManifestSegment("build:1", "build 1\n")],
   438        fromCheckpoint: 0,
   439        toCheckpoint: 1,
   440      })
   441  
   442      logs.append({
   443        spans: {
   444          "build:2": { manifestName: "be" },
   445        },
   446        segments: [
   447          newManifestSegment("build:2", "build 2\n"),
   448          newManifestSegment("build:2", "build 3\n"),
   449        ],
   450        fromCheckpoint: 1,
   451        toCheckpoint: 3,
   452      })
   453  
   454      logs.append({
   455        spans: {
   456          "": {},
   457        },
   458        segments: [newGlobalSegment("global line 1\n")],
   459        fromCheckpoint: 3,
   460        toCheckpoint: 4,
   461      })
   462  
   463      const logList = logs.toLogList(28)
   464      // log should be truncated
   465      expect(logList.segments?.length).toEqual(2)
   466      // order should be preserved
   467      expect(logList.segments![0].text).toEqual("build 3\n")
   468      expect(logList.segments![1].text).toEqual("global line 1\n")
   469  
   470      // only spans referenced by segments in the truncated output should exist
   471      const spans = logList.spans as { [key: string]: Proto.webviewLogSpan }
   472      expect(Object.keys(spans).length).toEqual(2)
   473      expect(spans["build:2"].manifestName).toEqual("be")
   474      expect(spans["_"].manifestName).toEqual("")
   475    })
   476  
   477    it("removes manifests", () => {
   478      let logs = new LogStore()
   479  
   480      logs.append({
   481        spans: {
   482          "build:1": { manifestName: "keep" },
   483          "build:2": { manifestName: "purge" },
   484          "": {},
   485        },
   486        segments: [
   487          newGlobalSegment("global line 1\n"),
   488          newManifestSegment("build:1", "start of line - "),
   489          newManifestSegment("build:2", "build 2\n"),
   490          newManifestSegment("build:1", "middle of line - "),
   491          newManifestSegment("build:2", "build 3\n"),
   492          newManifestSegment("build:1", "end of line\n"),
   493        ],
   494        fromCheckpoint: 0,
   495        toCheckpoint: 4,
   496      })
   497  
   498      logs.removeSpans(["build:2"])
   499      expect(logs.segments).toHaveLength(4)
   500      expect(logs.segmentToLine).toHaveLength(4)
   501      expect(Object.keys(logs.spans).sort()).toEqual(["_", "build:1"])
   502  
   503      expect(logLinesToString(logs.allLog(), true)).toEqual(
   504        "global line 1\nkeep        ┊ start of line - middle of line - end of line"
   505      )
   506      expect(logLinesToString(logs.manifestLog("be"), false)).toEqual("")
   507    })
   508  
   509    it("weights on recently and length", () => {
   510      let logs = new LogStore()
   511      expect(
   512        logs.heaviestManifestName({
   513          a: { name: "a", byteCount: 100, start: "2020-04" },
   514          b: { name: "b", byteCount: 100, start: "2021-04" },
   515        })
   516      ).toEqual("a")
   517  
   518      expect(
   519        logs.heaviestManifestName({
   520          a: { name: "a", byteCount: 100, start: "2020-04" },
   521          b: { name: "b", byteCount: 1000, start: "2021-04" },
   522        })
   523      ).toEqual("b")
   524  
   525      expect(
   526        logs.heaviestManifestName({
   527          a: { name: "a", byteCount: 100, start: "2020-04" },
   528          b: { name: "b", byteCount: 150, start: "2021-04" },
   529        })
   530      ).toEqual("a")
   531    })
   532  
   533    it("truncates output for snapshots", () => {
   534      let logs = new LogStore()
   535      logs.maxLogLength = 40
   536  
   537      logs.append({
   538        spans: {
   539          "build:1": { manifestName: "fe" },
   540        },
   541        segments: [newManifestSegment("build:1", "build 1\n")],
   542        fromCheckpoint: 0,
   543        toCheckpoint: 1,
   544      })
   545  
   546      logs.append({
   547        spans: {
   548          "build:2": { manifestName: "be" },
   549        },
   550        segments: [
   551          newManifestSegment("build:2", "build 2\n"),
   552          newManifestSegment("build:2", "build 3\n"),
   553          newManifestSegment("build:2", "build 4\n"),
   554          newManifestSegment("build:2", "build 5\n"),
   555          newManifestSegment("build:2", "build 6\n"),
   556          newManifestSegment("build:2", "build 7\n"),
   557        ],
   558        fromCheckpoint: 1,
   559        toCheckpoint: 7,
   560      })
   561  
   562      logs.append({
   563        spans: {
   564          "": {},
   565        },
   566        segments: [newGlobalSegment("global line 1\n")],
   567        fromCheckpoint: 7,
   568        toCheckpoint: 8,
   569      })
   570  
   571      expect(logLinesToString(logs.allLog(), true)).toEqual(
   572        "fe          ┊ build 1\nbe          ┊ build 7\nglobal line 1"
   573      )
   574    })
   575  
   576    it("handles starred resource logs", () => {
   577      let logs = new LogStore()
   578      logs.append({
   579        spans: {
   580          "": {},
   581          "build:1": { manifestName: "res1" },
   582          "build:2": { manifestName: "res2" },
   583          "build:3": { manifestName: "res3" },
   584        },
   585        segments: [
   586          newManifestSegment("build:1", "build 1\n"),
   587          newManifestSegment("build:2", "build 2\n"),
   588          newManifestSegment("build:3", "build 3\n"),
   589        ],
   590        fromCheckpoint: 0,
   591        toCheckpoint: 3,
   592      })
   593  
   594      let patch = logs.starredLogPatchSet(["res1", "res2"], 0)
   595      expect(logLinesToString(patch.lines, false)).toEqual("build 1\nbuild 2")
   596  
   597      logs.append({
   598        spans: {
   599          "": {},
   600          "build:1": { manifestName: "res1" },
   601          "build:2": { manifestName: "res2" },
   602          "build:3": { manifestName: "res3" },
   603        },
   604        segments: [
   605          newManifestSegment("build:1", "build 4\n"),
   606          newManifestSegment("build:2", "build 5\n"),
   607          newManifestSegment("build:3", "build 6\n"),
   608        ],
   609        fromCheckpoint: 3,
   610        toCheckpoint: 6,
   611      })
   612  
   613      let patch3 = logs.starredLogPatchSet(["res1", "res2"], patch.checkpoint)
   614      expect(logLinesToString(patch3.lines, false)).toEqual("build 4\nbuild 5")
   615    })
   616  })