diff --git a/HW_7/HW_7.Tests/HW_7.Tests.fsproj b/HW_7/HW_7.Tests/HW_7.Tests.fsproj new file mode 100644 index 0000000..dbeaf55 --- /dev/null +++ b/HW_7/HW_7.Tests/HW_7.Tests.fsproj @@ -0,0 +1,40 @@ + + + + net7.0 + + false + false + true + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive +all + + runtime; build; native; contentfiles; analyzers; buildtransitive +all + + + + + + + + + + + + + + + + diff --git a/HW_7/HW_7.Tests/Program.fs b/HW_7/HW_7.Tests/Program.fs new file mode 100644 index 0000000..0c4b1d7 --- /dev/null +++ b/HW_7/HW_7.Tests/Program.fs @@ -0,0 +1,4 @@ +module Program = + + [] + let main _ = 0 \ No newline at end of file diff --git a/HW_7/HW_7.Tests/UnitTests.fs b/HW_7/HW_7.Tests/UnitTests.fs new file mode 100644 index 0000000..27c6d83 --- /dev/null +++ b/HW_7/HW_7.Tests/UnitTests.fs @@ -0,0 +1,134 @@ +namespace HW_7.Tests + +open System.Net.Http +open Moq +open NUnit.Framework +open FsUnit +open System.Net +open System +open System.Threading +open HW_7.Lazy +open HW_7.MiniCrawler + +module Tests = + + let random = Random() + + let ``suppliers single thread`` = + seq { + TestCaseData(fun () -> (+) (random.Next(10)) <| random.Next(10)) + TestCaseData(fun () -> (*) (random.Next(10)) <| random.Next(100)) + } + + let ``lazy multithread`` = + seq { + TestCaseData(fun supplier -> LazyWithLock (supplier) :> ILazy) + TestCaseData(fun supplier -> LazyLockFree (supplier) :> ILazy) + } + + [] + let``SingleThreaded works correctly with value``(supplier : (unit -> int)) = + let lazyItem = LazySingleThread(supplier) :> ILazy + + let firstCalculation = lazyItem.Get() + let secondCalculation = lazyItem.Get() + let thirdCalculation = lazyItem.Get() + + firstCalculation |> should equal secondCalculation + firstCalculation |> should equal thirdCalculation + + [] + let MultithreadTestFunction (createLazy: (unit -> 'a) -> ILazy<'a>) = + + let manualResetEvent = new ManualResetEvent(false) + + let counter = ref 0 + + let supplier () = + manualResetEvent.WaitOne() |> ignore + Interlocked.Increment counter |> ignore + obj () + + manualResetEvent.Reset() |> ignore + let lazyItem = createLazy supplier + + let tasks = Seq.init 100 (fun _ -> async { return lazyItem.Get()}) + let tasksToRun = tasks |> Async.Parallel + manualResetEvent.Set() + let results = tasksToRun |> Async.RunSynchronously + + let compare_item = Seq.item 0 results + + results |> Seq.forall (fun item -> obj.ReferenceEquals(item, compare_item)) |> should equal true + + [] + let ``case two links are correct`` () = + let mockHttp = new Mock() + mockHttp.Setup(fun c -> c.GetAsync(It.IsAny())).ReturnsAsync( + let response = new HttpResponseMessage(HttpStatusCode.OK) + response.Content <- new StringContent("Page 1Page 2") + response + ) |> ignore + + + mockHttp.Setup(fun c -> c.GetAsync("https://example.com/page1")).ReturnsAsync( + let response = new HttpResponseMessage(HttpStatusCode.OK) + response.Content <- new StringContent("Page 1 content") + response + ) |> ignore + + mockHttp.Setup(fun c -> c.GetAsync("https://example.com/page2")).ReturnsAsync( + let response = new HttpResponseMessage(HttpStatusCode.OK) + response.Content <- new StringContent("Page 2 content") + response + ) |> ignore + + + let result = Async.RunSynchronously (downloadAndPrintPageSize mockHttp.Object "https://example.com") + + match result with + | Some sizes -> + Assert.AreEqual(2, sizes.Length) + Assert.Contains(("https://example.com/page1", Some 14), sizes) + Assert.Contains(("https://example.com/page2", Some 14), sizes) + | None -> Assert.Fail("Expected Some result") + + [] + let ``case one link is incorrect`` () = + let mockHttp = new Mock() + mockHttp.Setup(fun c -> c.GetAsync(It.IsAny())).ReturnsAsync( + let response = new HttpResponseMessage(HttpStatusCode.OK) + response.Content <- new StringContent("Page 1Page 2") + response + ) |> ignore + + mockHttp.Setup(fun c -> c.GetAsync("https://site.com/page_correct")).ReturnsAsync( + let response = new HttpResponseMessage(HttpStatusCode.OK) + response.Content <- new StringContent("Page 1 content") + response + ) |> ignore + + mockHttp.Setup(fun c -> c.GetAsync("https://site.com/page_incorrect")).ReturnsAsync( + let response = new HttpResponseMessage(HttpStatusCode.NotFound) + response + ) |> ignore + + let result = Async.RunSynchronously (downloadAndPrintPageSize mockHttp.Object "https://site.com") + + match result with + | Some sizes -> + Assert.AreEqual(2, sizes.Length) + Assert.Contains(("https://site.com/page_correct", Some 14), sizes) + Assert.Contains(("https://site.com/page_incorrect", None), sizes) + | None -> Assert.Fail("Expected Some result") + + [] + let ``incorrect url should throw exeption`` () = + let mockHttp = new Mock() + mockHttp.Setup(fun c -> c.GetAsync("https://page_incorrect")).ReturnsAsync( + let response = new HttpResponseMessage(HttpStatusCode.NotFound) + response.Content <- new StringContent("Page 1 content") + response + ) |> ignore + (fun () -> downloadAndPrintPageSize mockHttp.Object "https://page_incorrect" |> Async.RunSynchronously |> ignore) + |> should throw typeof diff --git a/HW_7/HW_7.sln b/HW_7/HW_7.sln new file mode 100644 index 0000000..1666a3a --- /dev/null +++ b/HW_7/HW_7.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 25.0.1706.10 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "HW_7", "HW_7\HW_7.fsproj", "{F1711BB9-3559-4373-BB05-72A984B3BD48}" +EndProject +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "HW_7.Tests", "HW_7.Tests\HW_7.Tests.fsproj", "{C60BE216-43E7-43FC-A2BF-76B1DACF93E4}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {F1711BB9-3559-4373-BB05-72A984B3BD48}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F1711BB9-3559-4373-BB05-72A984B3BD48}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F1711BB9-3559-4373-BB05-72A984B3BD48}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F1711BB9-3559-4373-BB05-72A984B3BD48}.Release|Any CPU.Build.0 = Release|Any CPU + {C60BE216-43E7-43FC-A2BF-76B1DACF93E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C60BE216-43E7-43FC-A2BF-76B1DACF93E4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C60BE216-43E7-43FC-A2BF-76B1DACF93E4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C60BE216-43E7-43FC-A2BF-76B1DACF93E4}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {5D56F09D-1A84-4AF5-B13B-08FBC8A8E199} + EndGlobalSection +EndGlobal diff --git a/HW_7/HW_7/HW_7.fsproj b/HW_7/HW_7/HW_7.fsproj new file mode 100644 index 0000000..e2551f0 --- /dev/null +++ b/HW_7/HW_7/HW_7.fsproj @@ -0,0 +1,13 @@ + + + + net7.0 + true + + + + + + + + diff --git a/HW_7/HW_7/Lazy.fs b/HW_7/HW_7/Lazy.fs new file mode 100644 index 0000000..5308f49 --- /dev/null +++ b/HW_7/HW_7/Lazy.fs @@ -0,0 +1,44 @@ +namespace HW_7 + +open System.Threading + +module Lazy = + + type ILazy<'a> = + abstract member Get: unit -> 'a + + type LazySingleThread<'a>(supplier: unit -> 'a) = + let mutable result = None + + interface ILazy<'a> with + member this.Get() = + match result with + | Some x -> x + | None -> + let value = supplier() + result <- Some (value) + value + + type LazyWithLock<'a>(supplier: unit -> 'a) = + let mutable result = None + let lockObject = obj() + + interface ILazy<'a> with + member this.Get() = + lock lockObject (fun () -> + match result with + | Some x -> x + | None -> + let value = supplier() + result <- Some (value) + value + ) + + type LazyLockFree<'a>(supplier: unit -> 'a) = + let mutable result = None + + interface ILazy<'a> with + member this.Get() = + match System.Threading.Interlocked.CompareExchange(&result, Some(supplier()), None) with + | Some value -> value + | None -> result.Value \ No newline at end of file diff --git a/HW_7/HW_7/MiniCrawler.fs b/HW_7/HW_7/MiniCrawler.fs new file mode 100644 index 0000000..1a36830 --- /dev/null +++ b/HW_7/HW_7/MiniCrawler.fs @@ -0,0 +1,52 @@ +namespace HW_7 + +open System.Net +open System.Text.RegularExpressions +open System.Threading.Tasks +open System.Net.Http + +module MiniCrawler = + + type IHttpClient = + abstract member GetAsync : string -> Task + + let readPage (client: IHttpClient) (link : string) = + + async { + let! response = client.GetAsync(link) |> Async.AwaitTask + response.EnsureSuccessStatusCode() |> ignore + let! content = response.Content.ReadAsStringAsync() |> Async.AwaitTask + return content + } + + let downloadAndPrintPageSize (client: IHttpClient) (url: string) = + + async { + try + let html = Async.RunSynchronously (readPage client url) + + + let linkRegex = new Regex(" Seq.cast + |> Seq.map (fun m -> m.Groups.[1].Value) + + let! content = + links + |> Seq.map (fun link -> + async { + try + let! content = readPage client link + return (link, Some content.Length) + with + | :? HttpRequestException -> return (link, None) + }) + |> Async.Parallel + + return Some content + with + | :? WebException -> + raise (WebException "Incorrect url") + return None + } \ No newline at end of file