CasperSecurity

Current Path : /usr/share/nodejs/tap-parser/
Upload File :
Current File : //usr/share/nodejs/tap-parser/index.js

'use strict'

// Transforms a stream of TAP into a stream of result objects
// and string comments.  Emits "results" event with summary.
const MiniPass = require('stream').PassThrough

const yaml = require('js-yaml')
const util = require('util')
const assert = require('assert')

// every line outside of a yaml block is one of these things, or
// a comment, or garbage.
const lineTypes = {
  testPoint: /^(not )?ok(?: ([0-9]+))?(?:(?: -)?( .*?))?(\{?)\n$/,
  pragma: /^pragma ([+-])([a-z]+)\n$/,
  bailout: /^bail out!(.*)\n$/i,
  version: /^TAP version ([0-9]+)\n$/i,
  childVersion: /^(    )+TAP version ([0-9]+)\n$/i,
  plan: /^([0-9]+)\.\.([0-9]+)(?:\s+(?:#\s*(.*)))?\n$/,
  subtest: /^# Subtest(?:: (.*))?\n$/,
  subtestIndent: /^    # Subtest(?:: (.*))?\n$/,
  comment: /^\s*#.*\n$/
}

const lineTypeNames = Object.keys(lineTypes)

const lineType = line => {
  for (let t in lineTypes) {
    const match = line.match(lineTypes[t])
    if (match)
      return [t, match]
  }
  return null
}

const parseDirective = line => {
  if (!line.trim())
    return false

  line = line.replace(/\{\s*$/, '').trim()
  const time = line.match(/^time=((?:[1-9][0-9]*|0)(?:\.[0-9]+)?)(ms|s)$/i)
  if (time) {
    let n = +time[1]
    if (time[2] === 's') {
      // JS does weird things with floats.  Round it off a bit.
      n *= 1000000
      n = Math.round(n)
      n /= 1000
    }
    return [ 'time', n ]
  }

  const type = line.match(/^(todo|skip)\b/i)
  if (!type)
    return false

  return [ type[1].toLowerCase(), line.substr(type[1].length).trim() || true ]
}

class Result {
  constructor (parsed, count) {
    const ok = !parsed[1]
    const id = +(parsed[2] || count + 1)
    let buffered = parsed[4]
    this.ok = ok
    this.id = id

    let rest = parsed[3] || ''
    let name
    rest = rest.replace(/([^\\]|^)((?:\\\\)*)#/g, '$1\n$2').split('\n')
    name = rest.shift()
    rest = rest.filter(r => r.trim()).join('#')

    // now, let's see if there's a directive in there.
    const dir = parseDirective(rest.trim())
    if (!dir)
      name += (rest ? '#' + rest : '') + buffered
    else {
      // handle buffered subtests with todo/skip on them, like
      // ok 1 - bar # todo foo {\n
      const dirKey = dir[0]
      const dirValue = dir[1]
      this[dirKey] = dirValue
    }

    if (/\{\s*$/.test(name)) {
      name = name.replace(/\{\s*$/, '')
      buffered = '{'
    }

    if (buffered === '{')
      this.buffered = true

    if (name)
      this.name = name.trim()
  }
}

class Parser extends MiniPass {
  constructor (options, onComplete) {
    if (typeof options === 'function') {
      onComplete = options
      options = {}
    }

    options = options || {}
    super(options)
    this.resume()

    if (onComplete)
      this.on('complete', onComplete)

    this.comments = []
    this.results = null
    this.braceLevel = null
    this.parent = options.parent || null
    this.failures = []
    if (options.passes)
      this.passes = []
    this.level = options.level || 0

    this.buffer = ''
    this.bail = !!options.bail
    this.bailingOut = false
    this.bailedOut = false
    this.syntheticBailout = false
    this.syntheticPlan = false
    this.omitVersion = !!options.omitVersion
    this.planStart = -1
    this.planEnd = -1
    this.planComment = ''
    this.yamlish = ''
    this.yind = ''
    this.child = null
    this.current = null
    this.maybeSubtest = null
    this.extraQueue = []
    this.buffered = options.buffered || null
    this.aborted = false
    this.preserveWhitespace = options.preserveWhitespace || false

    this.count = 0
    this.pass = 0
    this.fail = 0
    this.todo = 0
    this.skip = 0
    this.ok = true

    this.strict = options.strict || false
    this.pragmas = { strict: this.strict }

    this.postPlan = false
  }

  tapError (error, line) {
    if (line)
      this.emit('line', line)
    this.ok = false
    this.fail ++
    if (typeof error === 'string') {
      error = {
        tapError: error
      }
    }
    this.failures.push(error)
  }

  parseTestPoint (testPoint, line) {
    this.emitResult()
    if (this.bailedOut)
      return

    this.emit('line', line)
    const res = new Result(testPoint, this.count)
    if (this.planStart !== -1) {
      const lessThanStart = +res.id < this.planStart
      const greaterThanEnd = +res.id > this.planEnd
      if (lessThanStart || greaterThanEnd) {
        if (lessThanStart)
          res.tapError = 'id less than plan start'
        else
          res.tapError = 'id greater than plan end'
        res.plan = { start: this.planStart, end: this.planEnd }
        this.tapError(res)
      }
    }

    if (res.id) {
      if (!this.first || res.id < this.first)
        this.first = res.id
      if (!this.last || res.id > this.last)
        this.last = res.id
    }

    if (!res.skip && !res.todo)
      this.ok = this.ok && res.ok

    // hold onto it, because we might get yamlish diagnostics
    this.current = res
  }

  nonTap (data, didLine) {
    if (this.bailingOut && /^( {4})*\}\n$/.test(data))
      return

    if (this.strict) {
      const err = {
        tapError: 'Non-TAP data encountered in strict mode',
        data: data
      }
      this.tapError(err)
      if (this.parent)
        this.parent.tapError(err)
    }

    // emit each line, then the extra as a whole
    if (!didLine)
      data.split('\n').slice(0, -1).forEach(line => {
        line += '\n'
        if (this.current || this.extraQueue.length)
          this.extraQueue.push(['line', line])
        else
          this.emit('line', line)
      })

    if (this.current || this.extraQueue.length)
      this.extraQueue.push(['extra', data])
    else
      this.emit('extra', data)
  }

  plan (start, end, comment, line) {
    // not allowed to have more than one plan
    if (this.planStart !== -1) {
      this.nonTap(line)
      return
    }

    // can't put a plan in a child.
    if (this.child || this.yind) {
      this.nonTap(line)
      return
    }

    this.emitResult()
    if (this.bailedOut)
      return

    // 1..0 is a special case. Otherwise, end must be >= start
    if (end < start && end !== 0 && start !== 1) {
      if (this.strict)
        this.tapError({
          tapError: 'plan end cannot be less than plan start',
          plan: {
            start: start,
            end: end
          }
        }, line)
      else
        this.nonTap(line)
      return
    }

    this.planStart = start
    this.planEnd = end
    const p = { start: start, end: end }
    if (comment)
      this.planComment = p.comment = comment

    // This means that the plan is coming at the END of all the tests
    // Plans MUST be either at the beginning or the very end.  We treat
    // plans like '1..0' the same, since they indicate that no tests
    // will be coming.
    if (this.count !== 0 || this.planEnd === 0)
      this.postPlan = true

    this.emit('line', line)
    this.emit('plan', p)
  }

  resetYamlish () {
    this.yind = ''
    this.yamlish = ''
  }

  // that moment when you realize it's not what you thought it was
  yamlGarbage () {
    const yamlGarbage = this.yind + '---\n' + this.yamlish
    this.emitResult()
    if (this.bailedOut)
      return
    this.nonTap(yamlGarbage, true)
  }

  yamlishLine (line) {
    if (line === this.yind + '...\n') {
      // end the yaml block
      this.processYamlish()
    } else {
      this.yamlish += line
    }
  }

  processYamlish () {
    const yamlish = this.yamlish
    this.resetYamlish()

    let diags
    try {
      diags = yaml.load(yamlish)
    } catch (er) {
      this.nonTap(this.yind + '---\n' + yamlish + this.yind + '...\n', true)
      return
    }

    this.current.diag = diags
    // we still don't emit the result here yet, to support diags
    // that come ahead of buffered subtests.
  }

  write (chunk, encoding, cb) {
    if (this.aborted)
      return

    if (typeof encoding === 'string' && encoding !== 'utf8')
      chunk = new Buffer.from(chunk, encoding)

    if (Buffer.isBuffer(chunk))
      chunk += ''

    if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    this.buffer += chunk
    do {
      const match = this.buffer.match(/^.*\r?\n/)
      if (!match)
        break

      this.buffer = this.buffer.substr(match[0].length)
      this.parse(match[0])
    } while (this.buffer.length)

    if (cb)
      process.nextTick(cb)

    return true
  }

  end (chunk, encoding, cb) {
    if (chunk) {
      if (typeof encoding === 'function') {
        cb = encoding
        encoding = null
      }
      this.write(chunk, encoding)
    }

    if (this.buffer)
      this.write('\n')

    // if we have yamlish, means we didn't finish with a ...
    if (this.yamlish)
      this.yamlGarbage()

    this.emitResult()

    if (this.syntheticBailout && this.level === 0) {
      this.syntheticBailout = false
      let reason = this.bailedOut
      if (reason === true)
        reason = ''
      else
        reason = ' ' + reason
      this.emit('line', 'Bail out!' + reason + '\n')
    }

    let skipAll

    if (this.planEnd === 0 && this.planStart === 1) {
      skipAll = true
      if (this.count === 0) {
        this.ok = true
      } else {
        this.tapError('Plan of 1..0, but test points encountered')
      }
    } else if (!this.bailedOut && this.planStart === -1) {
      if (this.count === 0 && !this.syntheticPlan) {
        this.syntheticPlan = true
        if (this.buffered) {
          this.planStart = 1
          this.planEnd = 0
        } else
          this.plan(1, 0, 'no tests found', '1..0 # no tests found\n')
        skipAll = true
      } else {
        this.tapError('no plan')
      }
    } else if (this.ok && this.count !== (this.planEnd - this.planStart + 1)) {
      this.tapError('incorrect number of tests')
    }

    if (this.ok && !skipAll && this.first !== this.planStart) {
      this.tapError('first test id does not match plan start')
    }

    if (this.ok && !skipAll && this.last !== this.planEnd) {
      this.tapError('last test id does not match plan end')
    }

    this.emitComplete(skipAll)
    if (cb)
      process.nextTick(cb)
  }

  emitComplete (skipAll) {
    if (!this.results) {
      const res = this.results = new FinalResults(!!skipAll, this)

      if (!res.bailout) {
        // comment a bit at the end so we know what happened.
        // but don't repeat these comments if they're already present.
        if (res.plan.end !== res.count)
          this.emitComment('test count(' + res.count +
                           ') != plan(' + res.plan.end + ')', false, true)

        if (res.fail > 0 && !res.ok)
          this.emitComment('failed ' + res.fail +
                           (res.count > 1 ? ' of ' + res.count + ' tests'
                            : ' test'),
                           false, true)

        if (res.todo > 0)
          this.emitComment('todo: ' + res.todo, false, true)

        if (res.skip > 0)
          this.emitComment('skip: ' + res.skip, false, true)
      }

      this.emit('complete', this.results)
    }
  }

  version (version, line) {
    // If version is specified, must be at the very beginning.
    if (version >= 13 &&
        this.planStart === -1 &&
        this.count === 0 &&
        !this.current) {
      this.emit('line', line)
      this.emit('version', version)
    } else
      this.nonTap(line)
  }

  pragma (key, value, line) {
    // can't put a pragma in a child or yaml block
    if (this.child) {
      this.nonTap(line)
      return
    }

    this.emitResult()
    if (this.bailedOut)
      return
    // only the 'strict' pragma is currently relevant
    if (key === 'strict') {
      this.strict = value
    }
    this.pragmas[key] = value
    this.emit('line', line)
    this.emit('pragma', key, value)
  }

  bailout (reason, synthetic) {
    this.syntheticBailout = synthetic

    if (this.bailingOut)
      return

    // Guard because emitting a result can trigger a forced bailout
    // if the harness decides that failures should be bailouts.
    this.bailingOut = reason || true

    if (!synthetic)
      this.emitResult()
    else
      this.current = null

    this.bailedOut = this.bailingOut
    this.ok = false
    if (!synthetic) {
      // synthetic bailouts get emitted on end
      let line = 'Bail out!'
      if (reason)
        line += ' ' + reason
      this.emit('line', line + '\n')
    }
    this.emit('bailout', reason)
    if (this.parent) {
      this.end()
      this.parent.bailout(reason, true)
    }
  }

  clearExtraQueue () {
    for (let c = 0; c < this.extraQueue.length; c++) {
      this.emit(this.extraQueue[c][0], this.extraQueue[c][1])
    }
    this.extraQueue.length = 0
  }

  endChild () {
    if (this.child && (!this.bailingOut || this.child.count)) {
      this.child.end()
      this.child = null
    }
  }

  emitResult () {
    if (this.bailedOut)
      return

    this.endChild()
    this.resetYamlish()

    if (!this.current)
      return this.clearExtraQueue()

    const res = this.current
    this.current = null

    this.count++
    if (res.ok) {
      this.pass++
      if (this.passes)
        this.passes.push(res)
    } else {
      this.fail++
      if (!res.todo && !res.skip) {
        this.ok = false
        this.failures.push(res)
      }
    }

    if (res.skip)
      this.skip++

    if (res.todo)
      this.todo++

    this.emit('assert', res)
    if (this.bail && !res.ok && !res.todo && !res.skip && !this.bailingOut) {
      this.maybeChild = null
      const ind = new Array(this.level + 1).join('    ')
      let p
      for (p = this; p.parent; p = p.parent);
      const bailName = res.name ? ' # ' + res.name : ''
      p.parse(ind + 'Bail out!' + bailName + '\n')
    }
    this.clearExtraQueue()
  }

  // TODO: We COULD say that any "relevant tap" line that's indented
  // by 4 spaces starts a child test, and just call it 'unnamed' if
  // it does not have a prefix comment.  In that case, any number of
  // 4-space indents can be plucked off to try to find a relevant
  // TAP line type, and if so, start the unnamed child.
  startChild (line) {
    const maybeBuffered = this.current && this.current.buffered
    const unindentStream = !maybeBuffered && this.maybeChild
    const indentStream = !maybeBuffered && !unindentStream &&
      lineTypes.subtestIndent.test(line)
    const unnamed = !maybeBuffered && !unindentStream && !indentStream

    // If we have any other result waiting in the wings, we need to emit
    // that now.  A buffered test emits its test point at the *end* of
    // the child subtest block, so as to match streamed test semantics.
    if (!maybeBuffered)
      this.emitResult()

    if (this.bailedOut)
      return

    this.child = new Parser({
      bail: this.bail,
      parent: this,
      level: this.level + 1,
      buffered: maybeBuffered,
      preserveWhitespace: this.preserveWhitespace,
      omitVersion: true,
      strict: this.strict
    })

    this.child.on('complete', results => {
      if (!results.ok)
        this.ok = false
    })

    this.child.on('line', l => {
      if (l.trim() || this.preserveWhitespace)
        l = '    ' + l
      this.emit('line', l)
    })

    // Canonicalize the parsing result of any kind of subtest
    // if it's a buffered subtest or a non-indented Subtest directive,
    // then synthetically emit the Subtest comment
    line = line.substr(4)
    let subtestComment
    if (indentStream) {
      subtestComment = line
      line = null
    } else if (maybeBuffered) {
      subtestComment = '# Subtest: ' + this.current.name + '\n'
    } else {
      subtestComment = this.maybeChild || '# Subtest\n'
    }

    this.maybeChild = null
    this.child.name = subtestComment.substr('# Subtest: '.length).trim()

    // at some point, we may wish to move 100% to preferring
    // the Subtest comment on the parent level.  If so, uncomment
    // this line, and remove the child.emitComment below.
    // this.emit('comment', subtestComment)
    if (!this.child.buffered)
      this.emit('line', subtestComment)
    this.emit('child', this.child)
    this.child.emitComment(subtestComment, true)
    if (line)
      this.child.parse(line)
  }

  abort (message, extra) {
    if (this.child) {
      const b = this.child.buffered
      this.child.abort(message, extra)
      extra = null
      if (b)
        this.write('\n}\n')
    }

    let dump
    if (extra && Object.keys(extra).length) {
      try {
        dump = yaml.dump(extra).trimRight()
      } catch (er) {}
    }

    let y
    if (dump)
      y = '  ---\n  ' + dump.split('\n').join('\n  ') + '\n  ...\n'
    else
      y = '\n'
    let n = (this.count || 0) + 1
    if (this.current)
      n += 1

    if (this.planEnd !== -1 && this.planEnd < n && this.parent) {
      // skip it, let the parent do this.
      this.aborted = true
      return
    }

    let ind = '' // new Array(this.level + 1).join('    ')
    message = message.replace(/[\n\r\s\t]/g, ' ')
    let point = '\nnot ok ' + n + ' - ' + message + '\n' + y

    if (this.planEnd === -1)
      point += '1..' + n + '\n'

    this.write(point)
    this.aborted = true
    this.end()
  }

  emitComment (line, skipLine, noDuplicate) {
    if (line.trim().charAt(0) !== '#')
      line = '# ' + line

    if (line.slice(-1) !== '\n')
      line += '\n'

    if (noDuplicate && this.comments.indexOf(line) !== -1)
      return

    this.comments.push(line)
    if (this.current || this.extraQueue.length) {
      // no way to get here with skipLine being true
      this.extraQueue.push(['line', line])
      this.extraQueue.push(['comment', line])
    } else {
      if (!skipLine)
        this.emit('line', line)
      this.emit('comment', line)
    }
  }

  parse (line) {
    // normalize line endings
    line = line.replace(/\r\n$/, '\n')

    // sometimes empty lines get trimmed, but are still part of
    // a subtest or a yaml block.  Otherwise, nothing to parse!
    if (line === '\n') {
      if (this.child)
        line = '    ' + line
      else if (this.yind)
        line = this.yind + line
    }

    // If we're bailing out, then the only thing we want to see is the
    // end of a buffered child test.  Anything else should be ignored.
    // But!  if we're bailing out a nested child, and ANOTHER nested child
    // comes after that one, then we don't want the second child's } to
    // also show up, or it looks weird.
    if (this.bailingOut) {
      if (!/^\s*}\n$/.test(line))
        return
      else if (!this.braceLevel || line.length < this.braceLevel)
        this.braceLevel = line.length
      else
        return
    }

    // This allows omitting even parsing the version if the test is
    // an indented child test.  Several parsers get upset when they
    // see an indented version field.
    if (this.omitVersion && lineTypes.version.test(line) && !this.yind)
      return

    // check to see if the line is indented.
    // if it is, then it's either a subtest, yaml, or garbage.
    const indent = line.match(/^[ \t]*/)[0]
    if (indent) {
      this.parseIndent(line, indent)
      return
    }

    // In any case where we're going to emitResult, that can trigger
    // a bailout, so we need to only emit the line once we know that
    // isn't happening, to prevent cases where there's a bailout, and
    // then one more line of output.  That'll also prevent the case
    // where the test point is emitted AFTER the line that follows it.

    // buffered subtests must end with a }
    if (this.child && this.child.buffered && line === '}\n') {
      this.endChild()
      this.emit('line', line)
      this.emitResult()
      return
    }

    // just a \n, emit only if we care about whitespace
    const validLine = this.preserveWhitespace || line.trim() || this.yind
    if (line === '\n')
      return validLine && this.emit('line', line)

    // buffered subtest with diagnostics
    if (this.current && line === '{\n' &&
        !this.current.buffered &&
        !this.child) {
      this.emit('line', line)
      this.current.buffered = true
      return
    }

    // now we know it's not indented, so if it's either valid tap
    // or garbage.  Get the type of line.
    const type = lineType(line)
    if (!type) {
      this.nonTap(line)
      return
    }

    if (type[0] === 'comment') {
      this.emitComment(line)
      return
    }

    // if we have any yamlish, it's garbage now.  We tolerate non-TAP and
    // comments in the midst of yaml (though, perhaps, that's questionable
    // behavior), but any actual TAP means that the yaml block was just
    // not valid.
    if (this.yind)
      this.yamlGarbage()

    // If it's anything other than a comment or garbage, then any
    // maybeChild is just an unsatisfied promise.
    if (this.maybeChild) {
      this.emitComment(this.maybeChild)
      this.maybeChild = null
    }

    // nothing but comments can come after a trailing plan
    if (this.postPlan) {
      this.nonTap(line)
      return
    }

    // ok, now it's maybe a thing
    if (type[0] === 'bailout') {
      this.bailout(type[1][1].trim(), false)
      return
    }

    if (type[0] === 'pragma') {
      const pragma = type[1]
      this.pragma(pragma[2], pragma[1] === '+', line)
      return
    }

    if (type[0] === 'version') {
      const version = type[1]
      this.version(parseInt(version[1], 10), line)
      return
    }

    if (type[0] === 'plan') {
      const plan = type[1]
      this.plan(+plan[1], +plan[2], (plan[3] || '').trim(), line)
      return
    }

    // streamed subtests will end when this test point is emitted
    if (type[0] === 'testPoint') {
      // note: it's weird, but possible, to have a testpoint ending in
      // { before a streamed subtest which ends with a test point
      // instead of a }.  In this case, the parser gets confused, but
      // also, even beginning to handle that means doing a much more
      // involved multi-line parse.  By that point, the subtest block
      // has already been emitted as a 'child' event, so it's too late
      // to really do the optimal thing.  The only way around would be
      // to buffer up everything and do a multi-line parse.  This is
      // rare and weird, and a multi-line parse would be a bigger
      // rewrite, so I'm allowing it as it currently is.
      this.parseTestPoint(type[1], line)
      return
    }

    // We already detected nontap up above, so the only case left
    // should be a `# Subtest:` comment.  Ignore for coverage, but
    // include the error here just for good measure.
    /* istanbul ignore else */
    if (type[0] === 'subtest') {
      // this is potentially a subtest.  Not indented.
      // hold until later.
      this.maybeChild = line
    } else {
      throw new Error('Unhandled case: ' + type[0])
    }
  }

  parseIndent (line, indent) {
    // still belongs to the child, so pass it along.
    if (this.child && line.substr(0, 4) === '    ') {
      line = line.substr(4)
      this.child.write(line)
      return
    }

    // one of:
    // - continuing yaml block
    // - starting yaml block
    // - ending yaml block
    // - body of a new child subtest that was previously introduced
    // - An indented subtest directive
    // - A comment, or garbage

    // continuing/ending yaml block
    if (this.yind) {
      if (line.indexOf(this.yind) === 0) {
        this.emit('line', line)
        this.yamlishLine(line)
        return
      } else {
        // oops!  that was not actually yamlish, I guess.
        // this is a case where the indent is shortened mid-yamlish block
        // treat existing yaml as garbage, continue parsing this line
        this.yamlGarbage()
      }
    }


    // start a yaml block under a test point
    if (this.current && !this.yind && line === indent + '---\n') {
      this.yind = indent
      this.emit('line', line)
      return
    }

    // at this point, not yamlish, and not an existing child test.
    // We may have already seen an unindented Subtest directive, or
    // a test point that ended in { indicating a buffered subtest
    // Child tests are always indented 4 spaces.
    if (line.substr(0, 4) === '    ') {
      if (this.maybeChild ||
          this.current && this.current.buffered ||
          lineTypes.subtestIndent.test(line)) {
        this.startChild(line)
        return
      }

      // It's _something_ indented, if the indentation is divisible by
      // 4 spaces, and the result is actual TAP of some sort, then do
      // a child subtest for it as well.
      //
      // This will lead to some ambiguity in cases where there are multiple
      // levels of non-signaled subtests, but a Subtest comment in the
      // middle of them, which may or may not be considered "indented"
      // See the subtest-no-comment-mid-comment fixture for an example
      // of this.  As it happens, the preference is towards an indented
      // Subtest comment as the interpretation, which is the only possible
      // way to resolve this, since otherwise there's no way to distinguish
      // between an anonymous subtest with a non-indented Subtest comment,
      // and an indented Subtest comment.
      const s = line.match(/( {4})+(.*\n)$/)
      if (s[2].charAt(0) !== ' ') {
        // integer number of indentations.
        const type = lineType(s[2])
        if (type) {
          if (type[0] === 'comment') {
            this.emit('line', line)
            this.emitComment(line)
          } else {
            // it's relevant!  start as an "unnamed" child subtest
            this.startChild(line)
          }
          return
        }
      }
    }

    // at this point, it's either a non-subtest comment, or garbage.

    if (lineTypes.comment.test(line)) {
      this.emitComment(line)
      return
    }

    this.nonTap(line)
  }
}

class FinalResults {
  constructor (skipAll, self) {
    this.ok = self.ok
    this.count = self.count
    this.pass = self.pass
    this.fail = self.fail || 0
    this.bailout = self.bailedOut || false
    this.todo = self.todo || 0
    this.skip = skipAll ? self.count : self.skip || 0
    this.plan = new FinalPlan(skipAll, self)
    this.failures = self.failures
    if (self.passes)
      this.passes = self.passes
  }
}

class FinalPlan {
  constructor (skipAll, self) {
    this.start = self.planStart === -1 ? null : self.planStart
    this.end = self.planStart === -1 ? null : self.planEnd
    this.skipAll = skipAll
    this.skipReason = skipAll ? self.planComment : ''
    this.comment = self.planComment || ''
  }
}

module.exports = Parser
Hacker Blog, Shell İndir, Sql İnjection, XSS Attacks, LFI Attacks, Social Hacking, Exploit Bot, Proxy Tools, Web Shell, PHP Shell, Alfa Shell İndir, Hacking Training Set, DDoS Script, Denial Of Service, Botnet, RFI Attacks, Encryption
Telegram @BIBIL_0DAY