xref: /llvm-project/clang/utils/analyzer/SATestUtils.py (revision dd3c26a045c081620375a878159f536758baba6e)
1bf92c446SGeorge Karpenkovimport os
2bf92c446SGeorge Karpenkovimport sys
321bacc21SValeriy Savchenkoimport time
4bf92c446SGeorge Karpenkov
57cebfa4eSValeriy Savchenkofrom subprocess import CalledProcessError, check_call
621bacc21SValeriy Savchenkofrom typing import List, IO, Optional, Tuple
7bf92c446SGeorge Karpenkov
8c98872e3SValeriy Savchenko
97cebfa4eSValeriy Savchenkodef which(command: str, paths: Optional[str] = None) -> Optional[str]:
10bf92c446SGeorge Karpenkov    """which(command, [paths]) - Look up the given command in the paths string
11bf92c446SGeorge Karpenkov    (or the PATH environment variable, if unspecified)."""
12bf92c446SGeorge Karpenkov
13bf92c446SGeorge Karpenkov    if paths is None:
14*dd3c26a0STobias Hieta        paths = os.environ.get("PATH", "")
15bf92c446SGeorge Karpenkov
16bf92c446SGeorge Karpenkov    # Check for absolute match first.
17bf92c446SGeorge Karpenkov    if os.path.exists(command):
18bf92c446SGeorge Karpenkov        return command
19bf92c446SGeorge Karpenkov
20bf92c446SGeorge Karpenkov    # Would be nice if Python had a lib function for this.
21bf92c446SGeorge Karpenkov    if not paths:
22bf92c446SGeorge Karpenkov        paths = os.defpath
23bf92c446SGeorge Karpenkov
24bf92c446SGeorge Karpenkov    # Get suffixes to search.
25bf92c446SGeorge Karpenkov    # On Cygwin, 'PATHEXT' may exist but it should not be used.
26*dd3c26a0STobias Hieta    if os.pathsep == ";":
27*dd3c26a0STobias Hieta        pathext = os.environ.get("PATHEXT", "").split(";")
28bf92c446SGeorge Karpenkov    else:
29*dd3c26a0STobias Hieta        pathext = [""]
30bf92c446SGeorge Karpenkov
31bf92c446SGeorge Karpenkov    # Search the paths...
32bf92c446SGeorge Karpenkov    for path in paths.split(os.pathsep):
33bf92c446SGeorge Karpenkov        for ext in pathext:
34bf92c446SGeorge Karpenkov            p = os.path.join(path, command + ext)
35bf92c446SGeorge Karpenkov            if os.path.exists(p):
36bf92c446SGeorge Karpenkov                return p
37bf92c446SGeorge Karpenkov
38bf92c446SGeorge Karpenkov    return None
39bf92c446SGeorge Karpenkov
40bf92c446SGeorge Karpenkov
417cebfa4eSValeriy Savchenkodef has_no_extension(file_name: str) -> bool:
427cebfa4eSValeriy Savchenko    root, ext = os.path.splitext(file_name)
437cebfa4eSValeriy Savchenko    return ext == ""
44bf92c446SGeorge Karpenkov
45bf92c446SGeorge Karpenkov
467cebfa4eSValeriy Savchenkodef is_valid_single_input_file(file_name: str) -> bool:
477cebfa4eSValeriy Savchenko    root, ext = os.path.splitext(file_name)
487cebfa4eSValeriy Savchenko    return ext in (".i", ".ii", ".c", ".cpp", ".m", "")
49bf92c446SGeorge Karpenkov
50bf92c446SGeorge Karpenkov
5121bacc21SValeriy Savchenkodef time_to_str(time: float) -> str:
5221bacc21SValeriy Savchenko    """
5321bacc21SValeriy Savchenko    Convert given time in seconds into a human-readable string.
5421bacc21SValeriy Savchenko    """
5521bacc21SValeriy Savchenko    return f"{time:.2f}s"
5621bacc21SValeriy Savchenko
5721bacc21SValeriy Savchenko
5821bacc21SValeriy Savchenkodef memory_to_str(memory: int) -> str:
5921bacc21SValeriy Savchenko    """
6021bacc21SValeriy Savchenko    Convert given number of bytes into a human-readable string.
6121bacc21SValeriy Savchenko    """
6221bacc21SValeriy Savchenko    if memory:
6321bacc21SValeriy Savchenko        try:
6421bacc21SValeriy Savchenko            import humanize
65*dd3c26a0STobias Hieta
6621bacc21SValeriy Savchenko            return humanize.naturalsize(memory, gnu=True)
6721bacc21SValeriy Savchenko        except ImportError:
6821bacc21SValeriy Savchenko            # no formatter installed, let's keep it in bytes
6921bacc21SValeriy Savchenko            return f"{memory}B"
7021bacc21SValeriy Savchenko
7121bacc21SValeriy Savchenko    # If memory is 0, we didn't succeed measuring it.
7221bacc21SValeriy Savchenko    return "N/A"
7321bacc21SValeriy Savchenko
7421bacc21SValeriy Savchenko
7521bacc21SValeriy Savchenkodef check_and_measure_call(*popenargs, **kwargs) -> Tuple[float, int]:
7621bacc21SValeriy Savchenko    """
7721bacc21SValeriy Savchenko    Run command with arguments.  Wait for command to complete and measure
7821bacc21SValeriy Savchenko    execution time and peak memory consumption.
7921bacc21SValeriy Savchenko    If the exit code was zero then return, otherwise raise
8021bacc21SValeriy Savchenko    CalledProcessError.  The CalledProcessError object will have the
8121bacc21SValeriy Savchenko    return code in the returncode attribute.
8221bacc21SValeriy Savchenko
8321bacc21SValeriy Savchenko    The arguments are the same as for the call and check_call functions.
8421bacc21SValeriy Savchenko
8521bacc21SValeriy Savchenko    Return a tuple of execution time and peak memory.
8621bacc21SValeriy Savchenko    """
8721bacc21SValeriy Savchenko    peak_mem = 0
8821bacc21SValeriy Savchenko    start_time = time.time()
8921bacc21SValeriy Savchenko
9021bacc21SValeriy Savchenko    try:
9121bacc21SValeriy Savchenko        import psutil as ps
9221bacc21SValeriy Savchenko
9321bacc21SValeriy Savchenko        def get_memory(process: ps.Process) -> int:
9421bacc21SValeriy Savchenko            mem = 0
9521bacc21SValeriy Savchenko
9621bacc21SValeriy Savchenko            # we want to gather memory usage from all of the child processes
9721bacc21SValeriy Savchenko            descendants = list(process.children(recursive=True))
9821bacc21SValeriy Savchenko            descendants.append(process)
9921bacc21SValeriy Savchenko
10021bacc21SValeriy Savchenko            for subprocess in descendants:
10121bacc21SValeriy Savchenko                try:
10221bacc21SValeriy Savchenko                    mem += subprocess.memory_info().rss
10321bacc21SValeriy Savchenko                except (ps.NoSuchProcess, ps.AccessDenied):
10421bacc21SValeriy Savchenko                    continue
10521bacc21SValeriy Savchenko
10621bacc21SValeriy Savchenko            return mem
10721bacc21SValeriy Savchenko
10821bacc21SValeriy Savchenko        with ps.Popen(*popenargs, **kwargs) as process:
10921bacc21SValeriy Savchenko            # while the process is running calculate resource utilization.
110*dd3c26a0STobias Hieta            while process.is_running() and process.status() != ps.STATUS_ZOMBIE:
11121bacc21SValeriy Savchenko                # track the peak utilization of the process
11221bacc21SValeriy Savchenko                peak_mem = max(peak_mem, get_memory(process))
113*dd3c26a0STobias Hieta                time.sleep(0.5)
11421bacc21SValeriy Savchenko
11521bacc21SValeriy Savchenko            if process.is_running():
11621bacc21SValeriy Savchenko                process.kill()
11721bacc21SValeriy Savchenko
11821bacc21SValeriy Savchenko        if process.returncode != 0:
11921bacc21SValeriy Savchenko            cmd = kwargs.get("args")
12021bacc21SValeriy Savchenko            if cmd is None:
12121bacc21SValeriy Savchenko                cmd = popenargs[0]
12221bacc21SValeriy Savchenko            raise CalledProcessError(process.returncode, cmd)
12321bacc21SValeriy Savchenko
12421bacc21SValeriy Savchenko    except ImportError:
12521bacc21SValeriy Savchenko        # back off to subprocess if we don't have psutil installed
12621bacc21SValeriy Savchenko        peak_mem = 0
12721bacc21SValeriy Savchenko        check_call(*popenargs, **kwargs)
12821bacc21SValeriy Savchenko
12921bacc21SValeriy Savchenko    return time.time() - start_time, peak_mem
13021bacc21SValeriy Savchenko
13121bacc21SValeriy Savchenko
132*dd3c26a0STobias Hietadef run_script(
133*dd3c26a0STobias Hieta    script_path: str,
134*dd3c26a0STobias Hieta    build_log_file: IO,
135*dd3c26a0STobias Hieta    cwd: str,
136*dd3c26a0STobias Hieta    out=sys.stdout,
137*dd3c26a0STobias Hieta    err=sys.stderr,
138*dd3c26a0STobias Hieta    verbose: int = 0,
139*dd3c26a0STobias Hieta):
140bf92c446SGeorge Karpenkov    """
141bf92c446SGeorge Karpenkov    Run the provided script if it exists.
142bf92c446SGeorge Karpenkov    """
1437cebfa4eSValeriy Savchenko    if os.path.exists(script_path):
144bf92c446SGeorge Karpenkov        try:
1457cebfa4eSValeriy Savchenko            if verbose == 1:
1467cebfa4eSValeriy Savchenko                out.write(f"  Executing: {script_path}\n")
1477cebfa4eSValeriy Savchenko
148*dd3c26a0STobias Hieta            check_call(
149*dd3c26a0STobias Hieta                f"chmod +x '{script_path}'",
150*dd3c26a0STobias Hieta                cwd=cwd,
1517cebfa4eSValeriy Savchenko                stderr=build_log_file,
1527cebfa4eSValeriy Savchenko                stdout=build_log_file,
153*dd3c26a0STobias Hieta                shell=True,
154*dd3c26a0STobias Hieta            )
1557cebfa4eSValeriy Savchenko
156*dd3c26a0STobias Hieta            check_call(
157*dd3c26a0STobias Hieta                f"'{script_path}'",
158*dd3c26a0STobias Hieta                cwd=cwd,
1597cebfa4eSValeriy Savchenko                stderr=build_log_file,
1607cebfa4eSValeriy Savchenko                stdout=build_log_file,
161*dd3c26a0STobias Hieta                shell=True,
162*dd3c26a0STobias Hieta            )
1637cebfa4eSValeriy Savchenko
1647cebfa4eSValeriy Savchenko        except CalledProcessError:
165*dd3c26a0STobias Hieta            err.write(
166*dd3c26a0STobias Hieta                f"Error: Running {script_path} failed. "
167*dd3c26a0STobias Hieta                f"See {build_log_file.name} for details.\n"
168*dd3c26a0STobias Hieta            )
169bf92c446SGeorge Karpenkov            sys.exit(-1)
170bf92c446SGeorge Karpenkov
171bf92c446SGeorge Karpenkov
1727cebfa4eSValeriy Savchenkodef is_comment_csv_line(entries: List[str]) -> bool:
173bf92c446SGeorge Karpenkov    """
174bf92c446SGeorge Karpenkov    Treat CSV lines starting with a '#' as a comment.
175bf92c446SGeorge Karpenkov    """
1767cebfa4eSValeriy Savchenko    return len(entries) > 0 and entries[0].startswith("#")
177