From 19d47ed5f5352c019ac0d78553d686a7c47913db Mon Sep 17 00:00:00 2001 From: Chris Holt Date: Mon, 6 Nov 2017 11:32:15 -0600 Subject: [PATCH 01/13] Fix build warning for reporters --- src/canopy/reporters.fs | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/canopy/reporters.fs b/src/canopy/reporters.fs index 46f74d1a..171a2890 100644 --- a/src/canopy/reporters.fs +++ b/src/canopy/reporters.fs @@ -183,7 +183,7 @@ type TeamCityReporter(?logImagesToConsole: bool) = member this.setEnvironment env = () -type LiveHtmlReporter(browser : BrowserStartMode, driverPath : string, ?pinBrowserRight0: bool) = +type LiveHtmlReporter(browser : BrowserStartMode, driverPath : string, ?pinBrowserRight0: bool) = let pinBrowserRight = defaultArg pinBrowserRight0 true let consoleReporter : IReporter = new ConsoleReporter() :> IReporter @@ -198,27 +198,38 @@ type LiveHtmlReporter(browser : BrowserStartMode, driverPath : string, ?pinBrows let options = Chrome.ChromeOptions() options.AddArguments("--disable-extensions") options.AddArgument("disable-infobars") - options.AddArgument("test-type") //https://code.google.com/p/chromedriver/issues/detail?id=799 + options.AddArgument("test-type") //https://code.google.com/p/chromedriver/issues/detail?id=799 new Chrome.ChromeDriver(driverPath, options) :> IWebDriver + | ChromeHeadless -> + let options = Chrome.ChromeOptions() + options.AddArgument("--disable-extensions") + options.AddArgument("disable-infobars") + options.AddArgument("test-type") //https://code.google.com/p/chromedriver/issues/detail?id=799 + options.AddArgument("--headless") + new Chrome.ChromeDriver(driverPath, options) :> IWebDriver | ChromeWithOptions options -> new Chrome.ChromeDriver(driverPath, options) :> IWebDriver | ChromeWithOptionsAndTimeSpan(options, timeSpan) -> new Chrome.ChromeDriver(driverPath, options, timeSpan) :> IWebDriver | ChromeWithUserAgent userAgent -> raise(System.Exception("Sorry ChromeWithUserAgent can't be used for LiveHtmlReporter")) | ChromiumWithOptions options -> new Chrome.ChromeDriver(driverPath, options) :> IWebDriver | Firefox -> new Firefox.FirefoxDriver() :> IWebDriver | FirefoxWithProfile profile -> new Firefox.FirefoxDriver(profile) :> IWebDriver - | FirefoxWithPath path -> + | FirefoxWithPath path -> let options = new Firefox.FirefoxOptions() options.BrowserExecutableLocation <- path new Firefox.FirefoxDriver(options) :> IWebDriver | FirefoxWithUserAgent userAgent -> raise(System.Exception("Sorry FirefoxWithUserAgent can't be used for LiveHtmlReporter")) - | FirefoxWithPathAndTimeSpan(path, timespan) -> + | FirefoxWithPathAndTimeSpan(path, timespan) -> let options = new Firefox.FirefoxOptions() options.BrowserExecutableLocation <- path new Firefox.FirefoxDriver(Firefox.FirefoxDriverService.CreateDefaultService(), options, timespan) :> IWebDriver - | FirefoxWithProfileAndTimeSpan(profile, timespan) -> + | FirefoxWithProfileAndTimeSpan(profile, timespan) -> let options = new Firefox.FirefoxOptions() options.Profile <- profile new Firefox.FirefoxDriver(Firefox.FirefoxDriverService.CreateDefaultService(), options, timespan) :> IWebDriver + | FirefoxHeadless -> + let options = new Firefox.FirefoxOptions() + options.AddArgument("--headless") + new Firefox.FirefoxDriver(options) :> IWebDriver | Safari -> new Safari.SafariDriver() :> IWebDriver | PhantomJS | PhantomJSProxyNone -> raise(System.Exception("Sorry PhantomJS can't be used for LiveHtmlReporter")) | Remote(_,_) -> raise(System.Exception("Sorry Remote can't be used for LiveHtmlReporter")) From 5e8ef8a5afffecdca2cbd1faf9f6a6c48961d06f Mon Sep 17 00:00:00 2001 From: Chris Holt Date: Mon, 6 Nov 2017 12:02:32 -0600 Subject: [PATCH 02/13] Add basics for a load testing tool --- .../canopy.integration.fsproj | 5 +- src/canopy.integration/csharp.fs | 6 +- src/canopy.integration/loadTest.fs | 109 ++++++++++++++++++ tests/basictests/Program.fs | 14 ++- tests/basictests/basictests.fsproj | 7 +- tests/basictests/file1.fs | 8 +- tests/basictests/file2.fs | 8 +- tests/basictests/loadTestTests.fs | 10 ++ 8 files changed, 145 insertions(+), 22 deletions(-) create mode 100644 src/canopy.integration/loadTest.fs create mode 100644 tests/basictests/loadTestTests.fs diff --git a/src/canopy.integration/canopy.integration.fsproj b/src/canopy.integration/canopy.integration.fsproj index bd79a10f..4ecc19ab 100644 --- a/src/canopy.integration/canopy.integration.fsproj +++ b/src/canopy.integration/canopy.integration.fsproj @@ -1,4 +1,4 @@ - + Debug @@ -52,6 +52,7 @@ + @@ -96,4 +97,4 @@ - \ No newline at end of file + diff --git a/src/canopy.integration/csharp.fs b/src/canopy.integration/csharp.fs index 032cd155..7eeae851 100644 --- a/src/canopy.integration/csharp.fs +++ b/src/canopy.integration/csharp.fs @@ -2,11 +2,11 @@ -type integration () = +type integration () = - static member diffJson example actual = + static member diffJson example actual = let diff = jsonValidator.diff example actual let diffString = diff |> List.map (fun d -> match d with | jsonValidator.Missing s -> sprintf "Missing %s" s | jsonValidator.Extra s -> sprintf "Extra %s" s) ResizeArray(diffString) - static member validateJson example actual = jsonValidator.validate example actual \ No newline at end of file + static member validateJson example actual = jsonValidator.validate example actual diff --git a/src/canopy.integration/loadTest.fs b/src/canopy.integration/loadTest.fs new file mode 100644 index 00000000..b660735d --- /dev/null +++ b/src/canopy.integration/loadTest.fs @@ -0,0 +1,109 @@ +module loadTest + +open System + +let guid guid = System.Guid.Parse(guid) + +type SendFormData = + { + JobId : string + NumberOfRequests : string + MaxWorkers : string + Uri : string + } + +type SendData = + { + JobId : System.Guid + NumberOfRequests : int + MaxWorkers : int + Uri : string + } + +let convertSendData (sendFormData : SendFormData) = + { + JobId = guid sendFormData.JobId + NumberOfRequests = int sendFormData.NumberOfRequests + MaxWorkers = int sendFormData.MaxWorkers + Uri = sendFormData.Uri + } + +//actor stuff +type actor<'t> = MailboxProcessor<'t> + +type Command = + | Process of (unit -> float) + +type Worker = + | Do + | Retire + +type Manager = + | Initialize of SendData * Command + | WorkerDone of float * actor + +let time f = + //just time it and swallow any exceptions for now + let stopWatch = System.Diagnostics.Stopwatch.StartNew() + try f() |> ignore + with _ -> () + stopWatch.Elapsed.TotalMilliseconds + +let newWorker (manager : actor) (command : Command): actor = + actor.Start(fun self -> + let rec loop () = + async { + let! msg = self.Receive () + match msg with + | Worker.Retire -> + return () + | Worker.Do -> + match command with + | Process f -> + let timing = time f + manager.Post(Manager.WorkerDone(timing, self)) + return! loop () + } + loop ()) + +let rec haveWorkersWork (idleWorkers : actor list) numberOfRequests = + match idleWorkers with + | [] -> numberOfRequests + | worker :: remainingWorkers -> + worker.Post(Worker.Do) + haveWorkersWork remainingWorkers (numberOfRequests - 1) + +let newManager () : actor = + let sw = System.Diagnostics.Stopwatch() + actor.Start(fun self -> + let rec loop numberOfRequests pendingRequests results = + async { + let! msg = self.Receive () + match msg with + | Manager.Initialize (sendData, command) -> + //build up a list of all the work to do + printfn "Requests: %A, Concurrency %A" sendData.NumberOfRequests sendData.MaxWorkers + let numberOfRequests = sendData.NumberOfRequests + let workers = [ 1 .. sendData.MaxWorkers ] |> List.map (fun _ -> newWorker self command) + let results = [] + let pendingRequests = sendData.MaxWorkers + sw.Restart() + let numberOfRequests = haveWorkersWork workers numberOfRequests + return! loop numberOfRequests pendingRequests results + | Manager.WorkerDone(ms, worker) -> + let results = ms :: results + if numberOfRequests > 0 then + let numberOfRequests = haveWorkersWork [worker] numberOfRequests + return! loop numberOfRequests pendingRequests results + else if pendingRequests > 1 then //if only 1 pendingRequest, then this that pendingRequest so we are done + let pendingRequests = pendingRequests - 1 + return! loop numberOfRequests pendingRequests results + else + sw.Stop() + let avg = results |> List.average + let min = results |> List.min + let max = results |> List.max + printfn "Total seconds: %A, Average ms: %A, Max ms: %A, Min ms: %A" sw.Elapsed.TotalSeconds avg max min + return! loop numberOfRequests pendingRequests results + } + loop 0 0 []) diff --git a/tests/basictests/Program.fs b/tests/basictests/Program.fs index dc6878ef..bd30a5dd 100644 --- a/tests/basictests/Program.fs +++ b/tests/basictests/Program.fs @@ -5,14 +5,14 @@ open OpenQA.Selenium open canopy open reporters -start chrome +//start chrome let mainBrowser = browser elementTimeout <- 3.0 compareTimeout <- 3.0 pageTimeout <- 3.0 runFailedContextsFirst <- false -reporter <- new LiveHtmlReporter(Chrome, configuration.chromeDir) :> IReporter -reporter.setEnvironment "My Machine" +//reporter <- new LiveHtmlReporter(Chrome, configuration.chromeDir) :> IReporter +//reporter.setEnvironment "My Machine" configuration.failureMessagesThatShoulBeTreatedAsSkip <- ["Skip me when I fail"] configuration.failScreenshotFileName <- @@ -22,12 +22,14 @@ configuration.failScreenshotFileName <- let stamp = DateTime.Now.ToString("MMM-d_HH-mm-ss") sprintf "%s_%s_%s" suiteContext cleanName stamp) -failFast := true +failFast := false + +loadTestTests.all() jsonValidatorTests.all() -file1.all() -file2.all() +//file1.all() +//file2.all() context "context1" once (fun _ -> Console.WriteLine "once") diff --git a/tests/basictests/basictests.fsproj b/tests/basictests/basictests.fsproj index 5084c9f6..e32af96d 100644 --- a/tests/basictests/basictests.fsproj +++ b/tests/basictests/basictests.fsproj @@ -1,4 +1,4 @@ - + Debug @@ -54,6 +54,7 @@ + @@ -82,7 +83,7 @@ True -