diff options
Diffstat (limited to 'spec/frontend/ci/job_details/store/utils_spec.js')
-rw-r--r-- | spec/frontend/ci/job_details/store/utils_spec.js | 683 |
1 files changed, 232 insertions, 451 deletions
diff --git a/spec/frontend/ci/job_details/store/utils_spec.js b/spec/frontend/ci/job_details/store/utils_spec.js index 8fc4eeb0ca8..6105c53a306 100644 --- a/spec/frontend/ci/job_details/store/utils_spec.js +++ b/spec/frontend/ci/job_details/store/utils_spec.js @@ -1,524 +1,305 @@ +import { logLinesParser } from '~/ci/job_details/store/utils'; + import { - logLinesParser, - updateIncrementalJobLog, - parseHeaderLine, - parseLine, - addDurationToHeader, - isCollapsibleSection, - findOffsetAndRemove, - getNextLineNumber, -} from '~/ci/job_details/store/utils'; -import { - mockJobLog, - originalTrace, - regularIncremental, - regularIncrementalRepeated, - headerTrace, - headerTraceIncremental, - collapsibleTrace, - collapsibleTraceIncremental, + mockJobLines, + mockEmptySection, + mockContentSection, + mockContentSectionClosed, + mockContentSectionHiddenDuration, + mockContentSubsection, + mockTruncatedBottomSection, + mockTruncatedTopSection, } from '../components/log/mock_data'; describe('Jobs Store Utils', () => { - describe('parseHeaderLine', () => { - it('returns a new object with the header keys and the provided line parsed', () => { - const headerLine = { content: [{ text: 'foo' }] }; - const parsedHeaderLine = parseHeaderLine(headerLine, 2); + describe('logLinesParser', () => { + it('parses plain lines', () => { + const result = logLinesParser(mockJobLines); - expect(parsedHeaderLine).toEqual({ - isClosed: false, - isHeader: true, - line: { - ...headerLine, - lineNumber: 2, - }, - lines: [], + expect(result).toEqual({ + lines: [ + { + offset: 0, + content: [ + { + text: 'Running with gitlab-runner 12.1.0 (de7731dd)', + style: 'term-fg-l-cyan term-bold', + }, + ], + lineNumber: 1, + }, + { + offset: 1001, + content: [{ text: ' on docker-auto-scale-com 8a6210b8' }], + lineNumber: 2, + }, + ], + sections: {}, }); }); - it('pre-closes a section when specified in options', () => { - const headerLine = { content: [{ text: 'foo' }], section_options: { collapsed: 'true' } }; - - const parsedHeaderLine = parseHeaderLine(headerLine, 2); - - expect(parsedHeaderLine.isClosed).toBe(true); - }); - - it('expands all pre-closed sections if hash is present', () => { - const headerLine = { content: [{ text: 'foo' }], section_options: { collapsed: 'true' } }; - - const parsedHeaderLine = parseHeaderLine(headerLine, 2, '#L33'); - - expect(parsedHeaderLine.isClosed).toBe(false); - }); - }); - - describe('parseLine', () => { - it('returns a new object with the lineNumber key added to the provided line object', () => { - const line = { content: [{ text: 'foo' }] }; - const parsed = parseLine(line, 1); - expect(parsed.content).toEqual(line.content); - expect(parsed.lineNumber).toEqual(1); - }); - }); + it('parses an empty section', () => { + const result = logLinesParser(mockEmptySection); - describe('addDurationToHeader', () => { - const duration = { - offset: 106, - content: [], - section: 'prepare-script', - section_duration: '00:03', - }; - - it('adds the section duration to the correct header', () => { - const parsed = [ - { - isClosed: false, - isHeader: true, - line: { - section: 'prepare-script', - content: [{ text: 'foo' }], + expect(result).toEqual({ + lines: [ + { + offset: 1002, + content: [ + { + text: 'Resolving secrets', + style: 'term-fg-l-cyan term-bold', + }, + ], + lineNumber: 1, + section: 'resolve-secrets', + isHeader: true, }, - lines: [], - }, - { - isClosed: false, - isHeader: true, - line: { - section: 'foo-bar', - content: [{ text: 'foo' }], + ], + sections: { + 'resolve-secrets': { + startLineNumber: 1, + endLineNumber: 1, + duration: '00:00', + isClosed: false, }, - lines: [], }, - ]; - - addDurationToHeader(parsed, duration); - - expect(parsed[0].line.section_duration).toEqual(duration.section_duration); - expect(parsed[1].line.section_duration).toEqual(undefined); + }); }); - it('does not add the section duration when the headers do not match', () => { - const parsed = [ - { - isClosed: false, - isHeader: true, - line: { - section: 'bar-foo', - content: [{ text: 'foo' }], + it('parses a section with content', () => { + const result = logLinesParser(mockContentSection); + + expect(result).toEqual({ + lines: [ + { + content: [{ text: 'Using Docker executor with image dev.gitlab.org3' }], + isHeader: true, + lineNumber: 1, + offset: 1004, + section: 'prepare-executor', }, - lines: [], - }, - { - isClosed: false, - isHeader: true, - line: { - section: 'foo-bar', - content: [{ text: 'foo' }], + { + content: [{ text: 'Docker executor with image registry.gitlab.com ...' }], + lineNumber: 2, + offset: 1005, + section: 'prepare-executor', + }, + { + content: [{ style: 'term-fg-l-green', text: 'Starting service ...' }], + lineNumber: 3, + offset: 1006, + section: 'prepare-executor', + }, + ], + sections: { + 'prepare-executor': { + startLineNumber: 1, + endLineNumber: 3, + duration: '00:09', + isClosed: false, }, - lines: [], - }, - ]; - addDurationToHeader(parsed, duration); - - expect(parsed[0].line.section_duration).toEqual(undefined); - expect(parsed[1].line.section_duration).toEqual(undefined); - }); - - it('does not add when content has no headers', () => { - const parsed = [ - { - section: 'bar-foo', - content: [{ text: 'foo' }], - lineNumber: 1, - }, - { - section: 'foo-bar', - content: [{ text: 'foo' }], - lineNumber: 2, }, - ]; - - addDurationToHeader(parsed, duration); - - expect(parsed[0].line).toEqual(undefined); - expect(parsed[1].line).toEqual(undefined); - }); - }); - - describe('isCollapsibleSection', () => { - const header = { - isHeader: true, - line: { - section: 'foo', - }, - }; - const line = { - lineNumber: 1, - section: 'foo', - content: [], - }; - - it('returns true when line belongs to the last section', () => { - expect(isCollapsibleSection([header], header, { section: 'foo', content: [] })).toEqual(true); - }); - - it('returns false when last line was not an header', () => { - expect(isCollapsibleSection([line], line, { section: 'bar' })).toEqual(false); - }); - - it('returns false when accumulator is empty', () => { - expect(isCollapsibleSection([], { isHeader: true }, { section: 'bar' })).toEqual(false); - }); - - it('returns false when section_duration is defined', () => { - expect(isCollapsibleSection([header], header, { section_duration: '10:00' })).toEqual(false); - }); - - it('returns false when `section` is not a match', () => { - expect(isCollapsibleSection([header], header, { section: 'bar' })).toEqual(false); - }); - - it('returns false when no parameters are provided', () => { - expect(isCollapsibleSection()).toEqual(false); - }); - }); - describe('logLinesParser', () => { - let result; - - beforeEach(() => { - result = logLinesParser(mockJobLog); - }); - - describe('regular line', () => { - it('adds a lineNumber property with correct index', () => { - expect(result[0].lineNumber).toEqual(1); - expect(result[1].lineNumber).toEqual(2); - expect(result[2].line.lineNumber).toEqual(3); - expect(result[3].line.lineNumber).toEqual(4); - expect(result[3].lines[0].lineNumber).toEqual(5); - expect(result[3].lines[1].lineNumber).toEqual(6); }); }); - describe('collapsible section', () => { - it('adds a `isClosed` property', () => { - expect(result[2].isClosed).toEqual(false); - expect(result[3].isClosed).toEqual(false); - }); - - it('adds a `isHeader` property', () => { - expect(result[2].isHeader).toEqual(true); - expect(result[3].isHeader).toEqual(true); - }); + it('parses a closed section with content', () => { + const result = logLinesParser(mockContentSectionClosed); - it('creates a lines array property with the content of the collapsible section', () => { - expect(result[3].lines.length).toEqual(2); - expect(result[3].lines[0].content).toEqual(mockJobLog[5].content); - expect(result[3].lines[1].content).toEqual(mockJobLog[6].content); + expect(result.sections['mock-closed-section']).toMatchObject({ + isClosed: true, }); }); - describe('section duration', () => { - it('adds the section information to the header section', () => { - expect(result[2].line.section_duration).toEqual(mockJobLog[3].section_duration); - expect(result[3].line.section_duration).toEqual(mockJobLog[7].section_duration); - }); - - it('does not add section duration as a line', () => { - expect(result[2].lines.includes(mockJobLog[5])).toEqual(false); - expect(result[3].lines.includes(mockJobLog[9])).toEqual(false); - }); - }); - }); - - describe('findOffsetAndRemove', () => { - describe('when last item is header', () => { - const existingLog = [ - { - isHeader: true, - isClosed: false, - line: { content: [{ text: 'bar' }], offset: 10, lineNumber: 1 }, - }, - ]; - - describe('and matches the offset', () => { - it('returns an array with the item removed', () => { - const newData = [{ offset: 10, content: [{ text: 'foobar' }] }]; - const result = findOffsetAndRemove(newData, existingLog); - - expect(result).toEqual([]); - }); - }); + it('parses a closed section as open when hash is present', () => { + const result = logLinesParser(mockContentSectionClosed, {}, '#L1'); - describe('and does not match the offset', () => { - it('returns the provided existing log', () => { - const newData = [{ offset: 110, content: [{ text: 'foobar' }] }]; - const result = findOffsetAndRemove(newData, existingLog); - - expect(result).toEqual(existingLog); - }); - }); - }); - - describe('when last item is a regular line', () => { - const existingLog = [{ content: [{ text: 'bar' }], offset: 10, lineNumber: 1 }]; - - describe('and matches the offset', () => { - it('returns an array with the item removed', () => { - const newData = [{ offset: 10, content: [{ text: 'foobar' }] }]; - const result = findOffsetAndRemove(newData, existingLog); - - expect(result).toEqual([]); - }); - }); - - describe('and does not match the fofset', () => { - it('returns the provided old log', () => { - const newData = [{ offset: 101, content: [{ text: 'foobar' }] }]; - const result = findOffsetAndRemove(newData, existingLog); - - expect(result).toEqual(existingLog); - }); + expect(result.sections['mock-closed-section']).toMatchObject({ + isClosed: false, }); }); - describe('when last item is nested', () => { - const existingLog = [ - { - isHeader: true, - isClosed: false, - lines: [{ offset: 101, content: [{ text: 'foobar' }], lineNumber: 2 }], - line: { - offset: 10, - lineNumber: 1, - section_duration: '10:00', - }, - }, - ]; - - describe('and matches the offset', () => { - it('returns an array with the last nested line item removed', () => { - const newData = [{ offset: 101, content: [{ text: 'foobar' }] }]; + it('parses a section with a hidden duration', () => { + const result = logLinesParser(mockContentSectionHiddenDuration); - const result = findOffsetAndRemove(newData, existingLog); - expect(result[0].lines).toEqual([]); - }); - }); - - describe('and does not match the offset', () => { - it('returns the provided old log', () => { - const newData = [{ offset: 120, content: [{ text: 'foobar' }] }]; - - const result = findOffsetAndRemove(newData, existingLog); - expect(result).toEqual(existingLog); - }); + expect(result.sections['mock-hidden-duration-section']).toMatchObject({ + hideDuration: true, + duration: '00:09', }); }); - describe('when no data is provided', () => { - it('returns an empty array', () => { - const result = findOffsetAndRemove(); - expect(result).toEqual([]); - }); - }); - }); - - describe('getNextLineNumber', () => { - describe('when there is no previous log', () => { - it('returns 1', () => { - expect(getNextLineNumber([])).toEqual(1); - expect(getNextLineNumber(undefined)).toEqual(1); - }); - }); + it('parses a section with a sub section', () => { + const result = logLinesParser(mockContentSubsection); - describe('when last line is 1', () => { - it('returns 1', () => { - const log = [ + expect(result).toEqual({ + lines: [ { - content: [], + offset: 0, + content: [{ text: 'Line 1' }], lineNumber: 1, + section: 'mock-section', + isHeader: true, }, - ]; - - expect(getNextLineNumber(log)).toEqual(2); - }); - }); - - describe('with unnested line', () => { - it('returns the lineNumber of the last item in the array', () => { - const log = [ { - content: [], - lineNumber: 10, + offset: 1002, + content: [{ text: 'Line 2 - section content' }], + lineNumber: 2, + section: 'mock-section', }, { - content: [], - lineNumber: 101, + offset: 1003, + content: [{ text: 'Line 3 - sub section header' }], + lineNumber: 3, + section: 'sub-section', + isHeader: true, }, - ]; - - expect(getNextLineNumber(log)).toEqual(102); - }); - }); - - describe('when last line is the header section', () => { - it('returns the lineNumber of the last item in the array', () => { - const log = [ { - content: [], - lineNumber: 10, + offset: 1004, + content: [{ text: 'Line 4 - sub section content' }], + lineNumber: 4, + section: 'sub-section', }, { + offset: 1005, + content: [{ text: 'Line 5 - sub sub section header with no content' }], + lineNumber: 5, + section: 'sub-sub-section', isHeader: true, - line: { - lineNumber: 101, - content: [], - }, - lines: [], }, - ]; - - expect(getNextLineNumber(log)).toEqual(102); - }); - }); - - describe('when last line is a nested line', () => { - it('returns the lineNumber of the last item in the nested array', () => { - const log = [ { - content: [], - lineNumber: 10, + offset: 1007, + content: [{ text: 'Line 6 - sub section content 2' }], + lineNumber: 6, + section: 'sub-section', }, { - isHeader: true, - line: { - lineNumber: 101, - content: [], - }, - lines: [ - { - lineNumber: 102, - content: [], - }, - { lineNumber: 103, content: [] }, - ], + offset: 1009, + content: [{ text: 'Line 7 - section content' }], + lineNumber: 7, + section: 'mock-section', + }, + { + offset: 1011, + content: [{ text: 'Job succeeded' }], + lineNumber: 8, + }, + ], + sections: { + 'mock-section': { + startLineNumber: 1, + endLineNumber: 7, + duration: '00:59', + isClosed: false, }, - ]; + 'sub-section': { + startLineNumber: 3, + endLineNumber: 6, + duration: '00:29', + isClosed: false, + }, + 'sub-sub-section': { + startLineNumber: 5, + endLineNumber: 5, + duration: '00:00', + isClosed: false, + }, + }, + }); + }); - expect(getNextLineNumber(log)).toEqual(104); + it('parsing repeated lines returns the same result', () => { + const result1 = logLinesParser(mockJobLines); + const result2 = logLinesParser(mockJobLines, { + currentLines: result1.lines, + currentSections: result1.sections, }); + + // `toBe` is used to ensure objects do not change and trigger Vue reactivity + expect(result1.lines).toBe(result2.lines); + expect(result1.sections).toBe(result2.sections); }); - }); - describe('updateIncrementalJobLog', () => { - describe('without repeated section', () => { - it('concats and parses both arrays', () => { - const oldLog = logLinesParser(originalTrace); - const result = updateIncrementalJobLog(regularIncremental, oldLog); + it('discards repeated lines and adds new ones', () => { + const result1 = logLinesParser(mockContentSection); + const result2 = logLinesParser( + [ + ...mockContentSection, + { + content: [{ text: 'offset is too low, is ignored' }], + offset: 500, + }, + { + content: [{ text: 'one new line' }], + offset: 1007, + }, + ], + { + currentLines: result1.lines, + currentSections: result1.sections, + }, + ); - expect(result).toEqual([ + expect(result2).toEqual({ + lines: [ { - offset: 1, - content: [ - { - text: 'Downloading', - }, - ], + content: [{ text: 'Using Docker executor with image dev.gitlab.org3' }], + isHeader: true, lineNumber: 1, + offset: 1004, + section: 'prepare-executor', }, { - offset: 2, - content: [ - { - text: 'log line', - }, - ], + content: [{ text: 'Docker executor with image registry.gitlab.com ...' }], lineNumber: 2, + offset: 1005, + section: 'prepare-executor', }, - ]); - }); - }); - - describe('with regular line repeated offset', () => { - it('updates the last line and formats with the incremental part', () => { - const oldLog = logLinesParser(originalTrace); - const result = updateIncrementalJobLog(regularIncrementalRepeated, oldLog); - - expect(result).toEqual([ { - offset: 1, - content: [ - { - text: 'log line', - }, - ], - lineNumber: 1, + content: [{ style: 'term-fg-l-green', text: 'Starting service ...' }], + lineNumber: 3, + offset: 1006, + section: 'prepare-executor', + }, + { + content: [{ text: 'one new line' }], + lineNumber: 4, + offset: 1007, }, - ]); + ], + sections: { + 'prepare-executor': { + startLineNumber: 1, + endLineNumber: 3, + duration: '00:09', + isClosed: false, + }, + }, }); }); - describe('with header line repeated', () => { - it('updates the header line and formats with the incremental part', () => { - const oldLog = logLinesParser(headerTrace); - const result = updateIncrementalJobLog(headerTraceIncremental, oldLog); + it('parses an interrupted job', () => { + const result = logLinesParser(mockTruncatedBottomSection); - expect(result).toEqual([ - { - isClosed: false, - isHeader: true, - line: { - offset: 1, - section_header: true, - content: [ - { - text: 'updated log line', - }, - ], - section: 'section', - lineNumber: 1, - }, - lines: [], - }, - ]); + expect(result.sections).toEqual({ + 'mock-section': { + startLineNumber: 1, + endLineNumber: Infinity, + duration: null, + isClosed: false, + }, }); }); - describe('with collapsible line repeated', () => { - it('updates the collapsible line and formats with the incremental part', () => { - const oldLog = logLinesParser(collapsibleTrace); - const result = updateIncrementalJobLog(collapsibleTraceIncremental, oldLog); + it('parses the ending of an incomplete section', () => { + const result = logLinesParser(mockTruncatedTopSection); - expect(result).toEqual([ - { - isClosed: false, - isHeader: true, - line: { - offset: 1, - section_header: true, - content: [ - { - text: 'log line', - }, - ], - section: 'section', - lineNumber: 1, - }, - lines: [ - { - offset: 2, - content: [ - { - text: 'updated log line', - }, - ], - section: 'section', - lineNumber: 2, - }, - ], - }, - ]); + expect(result.sections).toEqual({ + 'mock-section': { + startLineNumber: 0, + endLineNumber: 2, + duration: '00:59', + isClosed: false, + }, }); }); }); |