public void Exe_PostProc_Succeeds()
        {
            // Arrange
            string rootDir = TestUtils.CreateTestSpecificFolder(this.TestContext);

            using (InitializeNonTeamBuildEnvironment(rootDir))
            {
                string binDir = CalculateBinDir(rootDir);
                Directory.CreateDirectory(binDir);
                DummyExeHelper.CreateDummyPostProcessor(binDir, 0 /* success exit code */);

                MockBuildAgentUpdater mockUpdater = CreateValidUpdater(binDir, "http://h:9000");

                // Act
                TestLogger logger = CheckExecutionSucceeds(mockUpdater, "end", "other params", "yet.more.params");

                // Assert
                mockUpdater.AssertUpdateNotAttempted();
                mockUpdater.AssertVersionNotChecked();
                logger.AssertWarningsLogged(0);

                // The bootstrapper pass through any parameters it doesn't recognise so the post-processor
                // can decide whether to handle them or not
                string logPath = DummyExeHelper.AssertDummyPostProcLogExists(binDir, this.TestContext);
                DummyExeHelper.AssertExpectedLogContents(logPath,
                                                         "other params",
                                                         "yet.more.params");
            }
        }
        public void Exe_PostProc_Fails()
        {
            // Arrange
            string rootDir = TestUtils.CreateTestSpecificFolder(this.TestContext);

            using (InitializeNonTeamBuildEnvironment(rootDir))
            {
                string binDir = CalculateBinDir(rootDir);
                Directory.CreateDirectory(binDir);
                DummyExeHelper.CreateDummyPostProcessor(binDir, 1 /* post-proc fails */);

                MockBuildAgentUpdater mockUpdater = CreateValidUpdater(binDir, "http://h:9000");

                // Act
                TestLogger logger = CheckExecutionFails(mockUpdater, "end");

                // Assert
                mockUpdater.AssertUpdateNotAttempted();
                mockUpdater.AssertVersionNotChecked();
                logger.AssertWarningsLogged(0);

                string logPath = DummyExeHelper.AssertDummyPostProcLogExists(binDir, this.TestContext);
                DummyExeHelper.AssertExpectedLogContents(logPath, null);
            }
        }
        public void ProcRunner_ArgumentQuotingForwardedByBatchScript()
        {
            // Checks arguments passed to a batch script which itself passes them on are correctly escaped

            // Arrange
            var testDir = TestUtils.CreateTestSpecificFolder(TestContext);
            // Create a dummy exe that will produce a log file showing any input args
            var exeName = DummyExeHelper.CreateDummyPostProcessor(testDir, 0);

            var batchName = TestUtils.WriteBatchFileForTest(TestContext, "\"" + exeName + "\" %*");

            var logger = new TestLogger();
            var args   = new ProcessRunnerArguments(batchName, true);

            var expected = new[] {
                "unquoted",
                "\"quoted\"",
                "\"quoted with spaces\"",
                "/test:\"quoted arg\"",
                "unquoted with spaces",
                "quote in \"the middle",
                "quotes \"& ampersands",
                "\"multiple \"\"\"      quotes \" ",
                "trailing backslash \\",
                "all special chars: \\ / : * ? \" < > | %",
                "injection \" > foo.txt",
                "injection \" & echo haha",
                "double escaping \\\" > foo.txt"
            };

            args.CmdLineArgs = expected;

            var runner = new ProcessRunner(logger);

            // Act
            var success = runner.Execute(args);

            // Assert
            Assert.IsTrue(success, "Expecting the process to have succeeded");
            Assert.AreEqual(0, runner.ExitCode, "Unexpected exit code");

            // Check that the public and private arguments are passed to the child process
            var exeLogFile = DummyExeHelper.AssertDummyPostProcLogExists(testDir, TestContext);

            DummyExeHelper.AssertExpectedLogContents(exeLogFile, expected);
        }
        public void ProcRunner_ArgumentQuoting()
        {
            // Checks arguments passed to the child process are correctly quoted

            // Arrange
            string testDir = TestUtils.CreateTestSpecificFolder(this.TestContext);
            // Create a dummy exe that will produce a log file showing any input args
            string exeName = DummyExeHelper.CreateDummyPostProcessor(testDir, 0);

            TestLogger             logger = new TestLogger();
            ProcessRunnerArguments args   = new ProcessRunnerArguments(exeName, logger);

            args.CmdLineArgs = new string[] {
                "unquoted",
                "\"quoted\"",
                "\"quoted with spaces\"",
                "/test:\"quoted arg\"",
                "unquoted with spaces",
                "quote in \"the middle",
                "quotes \"& ampersands",
                "\"multiple \"\"\"      quotes \" "
            };

            ProcessRunner runner = new ProcessRunner();

            // Act
            bool success = runner.Execute(args);

            // Assert
            Assert.IsTrue(success, "Expecting the process to have succeeded");
            Assert.AreEqual(0, runner.ExitCode, "Unexpected exit code");

            // Check that the public and private arguments are passed to the child process
            string exeLogFile = DummyExeHelper.AssertDummyPostProcLogExists(testDir, this.TestContext);

            DummyExeHelper.AssertExpectedLogContents(exeLogFile,
                                                     "unquoted",
                                                     "\"quoted\"",
                                                     "\"quoted with spaces\"",
                                                     "/test:\"quoted arg\"",
                                                     "unquoted with spaces",
                                                     "quote in \"the middle",
                                                     "quotes \"& ampersands",
                                                     "\"multiple \"\"\"      quotes \" ");
        }
        public void ProcRunner_ArgumentQuoting()
        {
            // Checks arguments passed to the child process are correctly quoted

            // Arrange
            var testDir = TestUtils.CreateTestSpecificFolder(TestContext);
            // Create a dummy exe that will produce a log file showing any input args
            var exeName = DummyExeHelper.CreateDummyPostProcessor(testDir, 0);

            var logger = new TestLogger();
            var args   = new ProcessRunnerArguments(exeName, false);

            var expected = new[] {
                "unquoted",
                "\"quoted\"",
                "\"quoted with spaces\"",
                "/test:\"quoted arg\"",
                "unquoted with spaces",
                "quote in \"the middle",
                "quotes \"& ampersands",
                "\"multiple \"\"\"      quotes \" ",
                "trailing backslash \\",
                "all special chars: \\ / : * ? \" < > | %",
                "injection \" > foo.txt",
                "injection \" & echo haha",
                "double escaping \\\" > foo.txt"
            };

            args.CmdLineArgs = expected;

            var runner = new ProcessRunner(logger);

            // Act
            var success = runner.Execute(args);

            // Assert
            success.Should().BeTrue("Expecting the process to have succeeded");
            runner.ExitCode.Should().Be(0, "Unexpected exit code");

            // Check that the public and private arguments are passed to the child process
            var exeLogFile = DummyExeHelper.AssertDummyPostProcLogExists(testDir, TestContext);

            DummyExeHelper.AssertExpectedLogContents(exeLogFile, expected);
        }
        [WorkItem(126)] // Exclude secrets from log data: http://jira.sonarsource.com/browse/SONARMSBRU-126
        public void ProcRunner_DoNotLogSensitiveData()
        {
            // Arrange
            var testDir = TestUtils.CreateTestSpecificFolder(TestContext);
            // Create a dummy exe that will produce a log file showing any input args
            var exeName = DummyExeHelper.CreateDummyPostProcessor(testDir, 0);

            var logger = new TestLogger();

            // Public args - should appear in the log
            var publicArgs = new string[]
            {
                "public1",
                "public2",
                "/d:sonar.projectKey=my.key"
            };

            var sensitiveArgs = new string[] {
                // Public args - should appear in the log
                "public1", "public2", "/dmy.key=value",

                // Sensitive args - should not appear in the log
                "/d:sonar.password=secret data password",
                "/d:sonar.login=secret data login",
                "/d:sonar.jdbc.password=secret data db password",
                "/d:sonar.jdbc.username=secret data db user name",

                // Sensitive args - different cases -> exclude to be on the safe side
                "/d:SONAR.jdbc.password=secret data db password upper",
                "/d:sonar.PASSWORD=secret data password upper",

                // Sensitive args - parameter format is slightly incorrect -> exclude to be on the safe side
                "/dsonar.login =secret data key typo",
                "sonar.password=secret data password typo"
            };

            var allArgs = sensitiveArgs.Union(publicArgs).ToArray();

            var runnerArgs = new ProcessRunnerArguments(exeName, false)
            {
                CmdLineArgs = allArgs
            };
            var runner = new ProcessRunner(logger);

            // Act
            var success = runner.Execute(runnerArgs);

            // Assert
            Assert.IsTrue(success, "Expecting the process to have succeeded");
            Assert.AreEqual(0, runner.ExitCode, "Unexpected exit code");

            // Check public arguments are logged but private ones are not
            foreach (var arg in publicArgs)
            {
                logger.AssertSingleDebugMessageExists(arg);
            }

            logger.AssertSingleDebugMessageExists("<sensitive data removed>");
            AssertTextDoesNotAppearInLog("secret", logger);

            // Check that the public and private arguments are passed to the child process
            var exeLogFile = DummyExeHelper.AssertDummyPostProcLogExists(testDir, TestContext);

            DummyExeHelper.AssertExpectedLogContents(exeLogFile, allArgs);
        }
        public void Exe__Version0_9Compatibility()
        {
            // Tests compatibility with the bootstrapper API used in v0.9
            // The pre-processor should be called if any arguments are passed.
            // The post-processor should be called if no arguments are passed.

            // Default settings:
            // There must be a default settings file next to the bootstrapper exe to supply
            // the necessary settings, and the bootstrapper should pass this settings path
            // to the pre-processor (since the pre-process is downloaded to a different
            // directory).

            // Arrange
            string rootDir = TestUtils.CreateTestSpecificFolder(this.TestContext);

            using (InitializeNonTeamBuildEnvironment(rootDir))
            {
                string binDir = CalculateBinDir(rootDir);
                MockBuildAgentUpdater mockUpdater = CreateValidUpdater(binDir, "http://host");

                mockUpdater.Updating += (sender, args) =>
                {
                    Assert.IsTrue(Directory.Exists(args.TargetDir), "Expecting the target directory to have been created");
                    DummyExeHelper.CreateDummyPreProcessor(args.TargetDir, 0 /* post-proc succeeds */);
                    DummyExeHelper.CreateDummyPostProcessor(args.TargetDir, 0 /* post-proc succeeds */);
                };

                // Create a default properties file next to the exe
                AnalysisProperties defaultProperties = new AnalysisProperties();
                defaultProperties.Add(new Property()
                {
                    Id = SonarProperties.HostUrl, Value = "http://host"
                });
                string defaultPropertiesFilePath = CreateDefaultPropertiesFile(defaultProperties);

                // Act
                try
                {
                    // Call the pre-processor
                    TestLogger logger = CheckExecutionSucceeds(mockUpdater, "/v:version", "/n:name", "/k:key");
                    logger.AssertWarningsLogged(1); // Should be warned once about the missing "begin" / "end"
                    logger.AssertSingleWarningExists(ArgumentProcessor.BeginVerb, ArgumentProcessor.EndVerb);

                    mockUpdater.AssertUpdateAttempted();
                    mockUpdater.AssertVersionChecked();

                    string logPath = DummyExeHelper.AssertDummyPreProcLogExists(binDir, this.TestContext);
                    DummyExeHelper.AssertExpectedLogContents(logPath,
                                                             "/v:version",
                                                             "/n:name",
                                                             "/k:key",
                                                             "/s:" + defaultPropertiesFilePath);

                    DummyExeHelper.AssertDummyPostProcLogDoesNotExist(binDir);

                    // Call the post-process (no arguments)
                    logger = CheckExecutionSucceeds(mockUpdater);

                    logPath = DummyExeHelper.AssertDummyPostProcLogExists(binDir, this.TestContext);
                    DummyExeHelper.AssertExpectedLogContents(logPath, null);

                    logger.AssertWarningsLogged(1); // Should be warned once about the missing "begin" / "end"
                    logger.AssertSingleWarningExists(ArgumentProcessor.BeginVerb, ArgumentProcessor.EndVerb);
                }
                finally
                {
                    File.Delete(defaultPropertiesFilePath);
                }
            }
        }