blob: bf24e8357d620a622fd9128ea933e5003c872a47 [file] [log] [blame]
Sean McAllister4b589082021-04-16 09:59:21 -06001# Copyright 2021 The Chromium OS Authors. All rights reserved.
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
Sean McAllister835696a2021-07-23 20:06:55 -060069def jqdiff(filea, fileb):
Sean McAllister4b589082021-04-16 09:59:21 -060070 """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
Sean McAllister4b589082021-04-16 09:59:21 -060075
76 Return:
77 Diff between jq output with -S (sorted keys) enabled
78 """
79
Sean McAllister835696a2021-07-23 20:06:55 -060080 # Enable recursive sort on arrays
Sean McAllister3c8d6542021-11-08 12:37:56 -070081 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"""
Sean McAllister835696a2021-07-23 20:06:55 -060097
Sean McAllister4b589082021-04-16 09:59:21 -060098 # 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