Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions HW_7/HW_7.Tests/HW_7.Tests.fsproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>

<IsPackable>false</IsPackable>
<GenerateProgramFile>false</GenerateProgramFile>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
<Compile Include="UnitTests.fs" />
<Compile Include="Program.fs" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
<PackageReference Include="NUnit" Version="4.1.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
<PackageReference Include="NUnit.Analyzers" Version="4.2.0"><IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.2"><IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="FsUnit" Version="6.0.0">
<GeneratePathProperty></GeneratePathProperty>
</PackageReference>
<PackageReference Include="FsCheck" Version="2.16.6">
<GeneratePathProperty></GeneratePathProperty>
</PackageReference>
<PackageReference Include="Moq" Version="4.20.70">
<GeneratePathProperty></GeneratePathProperty>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\HW_7\HW_7.fsproj" />
</ItemGroup>
</Project>
4 changes: 4 additions & 0 deletions HW_7/HW_7.Tests/Program.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module Program =

[<EntryPoint>]
let main _ = 0
134 changes: 134 additions & 0 deletions HW_7/HW_7.Tests/UnitTests.fs
Original file line number Diff line number Diff line change
@@ -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<obj>)
TestCaseData(fun supplier -> LazyLockFree (supplier) :> ILazy<obj>)
}

[<TestCaseSource("suppliers single thread")>]
let``SingleThreaded works correctly with value``(supplier : (unit -> int)) =
let lazyItem = LazySingleThread(supplier) :> ILazy<int>

let firstCalculation = lazyItem.Get()
let secondCalculation = lazyItem.Get()
let thirdCalculation = lazyItem.Get()

firstCalculation |> should equal secondCalculation
firstCalculation |> should equal thirdCalculation

[<TestCaseSource("lazy multithread")>]
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

[<Test>]
let ``case two links are correct`` () =
let mockHttp = new Mock<IHttpClient>()
mockHttp.Setup(fun c -> c.GetAsync(It.IsAny<string>())).ReturnsAsync(
let response = new HttpResponseMessage(HttpStatusCode.OK)
response.Content <- new StringContent("<html><a href='https://example.com/page1'>Page 1</a><a href='https://example.com/page2'>Page 2</a></html>")
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)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Лучше уж сразу весь список сравнить, чем поэлементно

Assert.Contains(("https://example.com/page2", Some 14), sizes)
| None -> Assert.Fail("Expected Some result")

[<Test>]
let ``case one link is incorrect`` () =
let mockHttp = new Mock<IHttpClient>()
mockHttp.Setup(fun c -> c.GetAsync(It.IsAny<string>())).ReturnsAsync(
let response = new HttpResponseMessage(HttpStatusCode.OK)
response.Content <- new StringContent("<html><a href='https://site.com/page_correct'>Page 1</a><a href='https://site.com/page_incorrect'>Page 2</a></html>")
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")

[<Test>]
let ``incorrect url should throw exeption`` () =
let mockHttp = new Mock<IHttpClient>()
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<HttpRequestException>
31 changes: 31 additions & 0 deletions HW_7/HW_7.sln
Original file line number Diff line number Diff line change
@@ -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
13 changes: 13 additions & 0 deletions HW_7/HW_7/HW_7.fsproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>

<ItemGroup>
<Compile Include="Lazy.fs" />
<Compile Include="MiniCrawler.fs" />
</ItemGroup>

</Project>
44 changes: 44 additions & 0 deletions HW_7/HW_7/Lazy.fs
Original file line number Diff line number Diff line change
@@ -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
52 changes: 52 additions & 0 deletions HW_7/HW_7/MiniCrawler.fs
Original file line number Diff line number Diff line change
@@ -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<HttpResponseMessage>

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("<a\s+href=\'(http[s]?://[^\'#]+)\'", RegexOptions.IgnoreCase)
let links =
linkRegex.Matches(html)
|> Seq.cast<Match>
|> 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 ->

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

В одном случае вы ловите HttpRequestException, в другом WebException, хотя логика одна. Подозрительно.

raise (WebException "Incorrect url")
return None

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Эта строчка недостижима, ведь выше кидается эксепшен

}