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