• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2021 The ChromiumOS Authors
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import itertools
6import multiprocessing
7import subprocess
8import sys
9import time
10
11# escape sequence to clear the current line and return to column 0
12CLEAR_LINE = "\033[2K\r"
13
14
15def clear_line(value):
16  """Return value with line clearing prefix added to it."""
17  return CLEAR_LINE + value
18
19
20class Spinner:
21  """Simple class to print a message and update a little spinning icon."""
22
23  def __init__(self, message):
24    self.message = message
25    self.spin = itertools.cycle("◐◓◑◒")
26
27  def tick(self):
28    sys.stderr.write(CLEAR_LINE + "[%c] %s" % (next(self.spin), self.message))
29
30  def done(self, success=True):
31    if success:
32      sys.stderr.write(CLEAR_LINE + "[✔] %s\n" % self.message)
33    else:
34      sys.stderr.write(CLEAR_LINE + "[✘] %s\n" % self.message)
35
36
37def call_and_spin(message, stdin, *cmd):
38  """Execute a command and print a nice status while we wait.
39
40    Args:
41      message (str): message to print while we wait (along with spinner)
42      stdin (bytes): array of bytes to send as the stdin (or None)
43      cmd   ([str]): command and any options and arguments
44
45    Return:
46      tuple of (data, status) containing process stdout and status
47  """
48
49  with multiprocessing.pool.ThreadPool(processes=1) as pool:
50    result = pool.apply_async(subprocess.run, (cmd,), {
51        'input': stdin,
52        'capture_output': True,
53        'text': True,
54    })
55
56    spinner = Spinner(message)
57    spinner.tick()
58
59    while not result.ready():
60      spinner.tick()
61      time.sleep(0.05)
62
63    process = result.get()
64    spinner.done(process.returncode == 0)
65
66    return process.stdout, process.returncode
67
68
69def jqdiff(filea, fileb):
70  """Diff two json files using jq with ordered keys.
71
72    Args:
73      filea (str): first file to compare
74      fileb (str): second file to compare
75
76    Return:
77      Diff between jq output with -S (sorted keys) enabled
78    """
79
80  # Enable recursive sort on arrays
81  filt = """
82def safeget(f):
83  if type == "object" then
84     f
85  else
86     .
87  end;
88
89walk(
90  if type == "array" then
91    sort_by(safeget(.hwidLabel), safeget(.id), .)
92  else
93    .
94  end
95)
96"""
97
98  # if inputs aren't declared, use a file that will (almost surely) never
99  # exist and pass -N to diff so it treats it as an empty file and gives a
100  # full diff
101  input0 = "<(jq -S '{}' {})".format(filt, filea) if filea else "/dev/__empty"
102  input1 = "<(jq -S '{}' {})".format(filt, fileb) if fileb else "/dev/__empty"
103
104  process = subprocess.run(
105      "diff -uN {} {}".format(input0, input1),
106      check=False,  # diff returns non-zero error status if there's a diff
107      shell=True,
108      text=True,
109      stdout=subprocess.PIPE,
110      stderr=subprocess.STDOUT,
111  )
112  return process.stdout
113