public void CreateBundleId_SameFriendlyName_DifferentBundleIdsWithoutSpacesAndSlashes()
        {
            var suffixGenerator = new SuffixGenerator();
            var target          = new BundleIdGenerator(suffixGenerator);

            var friendlyName = @".this is the : _ // """"
? * a friendly name";
            var actual       = target.CreateBundleId(friendlyName);

            Assert.NotEqual(target.CreateBundleId(friendlyName), actual);
            Assert.StartsWith("this_is_the_a_friendly_name_", actual);
        }
        private bool SortSymbols(string batchLocation, SymbolUploadBatch batch, string symsorterOutput, ISpan symsorterSpan)
        {
            var bundleId        = _bundleIdGenerator.CreateBundleId(batch.FriendlyName);
            var symsorterPrefix = batch.BatchType.ToSymsorterPrefix();

            var args = $"--ignore-errors -zz -o {symsorterOutput} --prefix {symsorterPrefix} --bundle-id {bundleId} {batchLocation}";

            symsorterSpan.SetExtra("args", args);

            var process = new Process
            {
                StartInfo = new ProcessStartInfo(_options.SymsorterPath, args)
                {
                    UseShellExecute        = false,
                    RedirectStandardOutput = true,
                    RedirectStandardError  = true,
                    CreateNoWindow         = true,
                    Environment            = { { "RUST_BACKTRACE", "full" } }
                }
            };

            string?lastLine = null;
            var    sw       = Stopwatch.StartNew();

            if (!process.Start())
            {
                throw new InvalidOperationException("symsorter failed to start");
            }

            while (!process.StandardOutput.EndOfStream)
            {
                var line = process.StandardOutput.ReadLine();
                if (string.IsNullOrWhiteSpace(line))
                {
                    continue;
                }
                _logger.LogInformation(line);
                lastLine = line;
            }

            while (!process.StandardError.EndOfStream)
            {
                var line = process.StandardError.ReadLine();
                if (string.IsNullOrWhiteSpace(line))
                {
                    continue;
                }
                _logger.LogWarning(line);
                lastLine = line;
            }

            const int waitUpToMs = 500_000;

            process.WaitForExit(waitUpToMs);
            sw.Stop();
            if (!process.HasExited)
            {
                throw new InvalidOperationException($"Timed out waiting for {batch.BatchId}. Symsorter args: {args}");
            }

            lastLine ??= string.Empty;

            if (process.ExitCode != 0)
            {
                throw new InvalidOperationException($"Symsorter exit code: {process.ExitCode}. Args: {args}");
            }

            _logger.LogInformation("Symsorter finished in {timespan} and logged last: {lastLine}",
                                   sw.Elapsed, lastLine);

            var match = Regex.Match(lastLine, "Sorted (?<count>\\d+) debug files");

            if (!match.Success)
            {
                _logger.LogError("Last line didn't match success: {lastLine}", lastLine);
                return(true);
            }

            _logger.LogInformation("Symsorter processed: {count}", match.Groups["count"].Value);
            return(false);
        }