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
36 changes: 36 additions & 0 deletions Crawler.Tests/Crawler.Tests.fsproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<Project Sdk="Microsoft.NET.Sdk">

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

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

<ItemGroup>
<Compile Include="CrawlerTest.fs" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageReference Include="NUnit" Version="4.2.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
<PackageReference Include="NUnit.Analyzers" Version="4.4.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.1">
<GeneratePathProperty></GeneratePathProperty>
</PackageReference>
<PackageReference Include="Moq" Version="4.20.72">
<GeneratePathProperty></GeneratePathProperty>
</PackageReference>
</ItemGroup>

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

open NUnit.Framework
open FsUnit
open Moq
open Сrawler.Сrawler
open System.Net.Http

let informationInTestUrl =
"<div class=\"><a href=\"http://example1\">\\\<a href=\"http://example2\">\""

let fstLink = "<a href=\"http://example1\">"
let sndLink = "<a href=\"http://example2\">"

[<Test>]
let ``Incorrect url test`` () =
let mockClient = new Mock<IHttpClient>()

mockClient
.Setup(fun c -> c.GetStringAsync(It.IsAny<string>()))
.Throws<HttpRequestException>()
|> ignore

let result =
downloadPagesFrom (mockClient.Object, "test_url") |> Async.RunSynchronously

result.IsNone |> should be True

[<Test>]
let ``Correct input — correct result`` () =
let mockClient = new Mock<IHttpClient>()

mockClient
.Setup(fun c -> c.GetStringAsync("test_url"))
.ReturnsAsync(informationInTestUrl)
|> ignore

mockClient.Setup(fun c -> c.GetStringAsync(fstLink)).ReturnsAsync("text link 1")
|> ignore

mockClient.Setup(fun c -> c.GetStringAsync(sndLink)).ReturnsAsync("text link 2")
|> ignore

let result =
downloadPagesFrom (mockClient.Object, "test_url") |> Async.RunSynchronously

result.IsNone |> should be False
let resultValue = result.Value

let expected =
seq {
(fstLink, 11)
(sndLink, 11)
}

resultValue |> should equal expected

[<Test>]
let ``Returns downloaded without errors links information test`` () =
let mockClient = new Mock<IHttpClient>()

mockClient
.Setup(fun c -> c.GetStringAsync("test_url"))
.ReturnsAsync(informationInTestUrl)
|> ignore

mockClient.Setup(fun c -> c.GetStringAsync(fstLink)).ReturnsAsync("text link 1")
|> ignore

mockClient
.Setup(fun c -> c.GetStringAsync(sndLink))
.Throws<HttpRequestException>()
|> ignore

let result =
downloadPagesFrom (mockClient.Object, "test_url") |> Async.RunSynchronously

result.IsNone |> should be False
let resultValue = result.Value

let expected = seq { (fstLink, 11) }

resultValue |> should equal expected
36 changes: 36 additions & 0 deletions Lazy.Tests/Lazy.Tests.fsproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<Project Sdk="Microsoft.NET.Sdk">

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

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

<ItemGroup>
<Compile Include="LazyTests.fs" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageReference Include="NUnit" Version="4.2.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
<PackageReference Include="NUnit.Analyzers" Version="4.4.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.1">
<GeneratePathProperty></GeneratePathProperty>
</PackageReference>
<PackageReference Include="FsCheck" Version="2.16.6">
<GeneratePathProperty></GeneratePathProperty>
</PackageReference>
</ItemGroup>

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

open NUnit.Framework
open FsCheck
open FsUnit
open System.Threading
open Lazy

[<Test>]
let ``Consistent Lazy returns same calculation result`` () =
let consistentLazyChecker (supplier: unit -> int) =
let newLazy = СonsistentLazy<int>(supplier) :> ILazy<int>
let fstResult = newLazy.Get()
let sndResult = newLazy.Get()
let thrdResult = newLazy.Get()
fstResult = sndResult && sndResult = thrdResult

Check.QuickThrowOnFailure consistentLazyChecker

let lazies =
[ (fun supplier -> LockLazy(supplier) :> ILazy<int>)
(fun supplier -> LockFreeLazy(supplier) :> ILazy<int>) ]
|> List.map (fun x -> TestCaseData(x))

[<TestCaseSource("lazies")>]
let ``Lock and free lock Lazies execute supplier one time`` (newILazy: (unit -> int) -> ILazy<int>) =
let event = new ManualResetEvent(false)
let mutable count = ref 0

let multiThreadLazy =
newILazy (fun () ->
Interlocked.Increment count)

let task =
async {
event.WaitOne() |> ignore
return multiThreadLazy.Get()
}

let taskSeq = Seq.init 10 (fun _ -> task) |> Async.Parallel
Thread.Sleep(100)
event.Set() |> ignore
let results = taskSeq |> Async.RunSynchronously
let mutable isEqual = true

for result in results do
isEqual <- (result = results.[0]) && isEqual

isEqual |> should be True
51 changes: 51 additions & 0 deletions Lazy/Lazy.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
namespace Lazy

open System.Threading

module Lazy =
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Тут тоже надо комментарии

/// Interface of Lazy.
type ILazy<'a> =
/// Returns new calculation of supplier value on first call.
/// <returns>Result of supplier.</returns>
abstract member Get: unit -> 'a

/// Represents Lazy working with one thread.
type СonsistentLazy<'a>(supplier: unit -> 'a) =
let mutable value: Option<'a> = None

interface ILazy<'a> with
/// <inheritdoc/>
member this.Get() =
match value with
| None ->
value <- Some(supplier ())
value.Value
| Some x -> x

/// Represents Lazy working with multithreads using lock.
type LockLazy<'a>(supplier: unit -> 'a) =
let mutable value: Option<'a> = None
let lockObj = obj ()

interface ILazy<'a> with
/// <inheritdoc/>
member this.Get() =
if value.IsNone then
lock (lockObj) (fun () ->
match value with
| None ->
value <- Some(supplier ())
value.Value
| Some x -> x)
else value.Value

/// Represents Lazy working with multithreads using lock free.
type LockFreeLazy<'a>(supplier: unit -> 'a) =
let mutable value: Option<'a> = None

interface ILazy<'a> with
/// <inheritdoc/>
member this.Get() =
match Interlocked.CompareExchange(&value, Some(supplier ()), None) with
| Some x -> x
| None -> value.Value
12 changes: 12 additions & 0 deletions Lazy/Lazy.fsproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">

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

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

</Project>
31 changes: 31 additions & 0 deletions Lazy/Lazy.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.14
MinimumVisualStudioVersion = 10.0.40219.1
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Lazy", "Lazy.fsproj", "{EDC31770-4B46-45AA-B1C3-E545B53BCD61}"
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Lazy.Tests", "..\Lazy.Tests\Lazy.Tests.fsproj", "{9E8DBA1A-61A6-4573-A20D-04959A4399C6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{EDC31770-4B46-45AA-B1C3-E545B53BCD61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EDC31770-4B46-45AA-B1C3-E545B53BCD61}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EDC31770-4B46-45AA-B1C3-E545B53BCD61}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EDC31770-4B46-45AA-B1C3-E545B53BCD61}.Release|Any CPU.Build.0 = Release|Any CPU
{9E8DBA1A-61A6-4573-A20D-04959A4399C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9E8DBA1A-61A6-4573-A20D-04959A4399C6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9E8DBA1A-61A6-4573-A20D-04959A4399C6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9E8DBA1A-61A6-4573-A20D-04959A4399C6}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D31E6CAF-FE9C-45C4-9FE3-024C5F8B0609}
EndGlobalSection
EndGlobal
55 changes: 55 additions & 0 deletions Сrawler/Crawler.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
namespace Сrawler

open System.Text.RegularExpressions
open System.Net.Http
open System.Threading.Tasks

module Сrawler =
type IHttpClient =
abstract member GetStringAsync: string -> Task<string>

let getHtmlLinks (urlContent: string) =
let regex = new Regex(@"<a href=""http://[^""]*"">", RegexOptions.IgnoreCase)
let matches = regex.Matches(urlContent) |> Seq.map (fun x -> x.Value)
matches

let downloadWebPageContent (client: IHttpClient, url: string) : Async<Option<string>> =
async {
try
let! response = client.GetStringAsync(url) |> Async.AwaitTask
return Some response
with :? HttpRequestException ->
printfn "Error downloading URL: %s" url
return None
}

let printDownloadingResult (results: seq<string * int>) =
for result in results do
printfn "%s %d" <| "url: " + fst result + "\nAmount of symbols:" <| snd result

let downloadPagesFrom (client: IHttpClient, url: string) =
async {
let! urlContent = downloadWebPageContent (client, url)

match urlContent with
| None -> return None
| Some content ->
let downloadingTasks =
getHtmlLinks content
|> Seq.map (fun link ->
async {
let! pageContent = downloadWebPageContent (client, link)
return link, pageContent
})
|> Async.Parallel

let! results = downloadingTasks

let unwrappedResults =
results
|> Seq.filter (fun (x: string * Option<string>) -> (snd x).IsSome)
|> Seq.map (fun (x: string * Option<string>) -> fst x, (snd x).Value.Length)

printDownloadingResult unwrappedResults
return Some unwrappedResults
}
17 changes: 17 additions & 0 deletions Сrawler/Сrawler.fsproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">

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

<ItemGroup>
<Compile Include="Crawler.fs" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="FSharp.Core" Version="9.0.100">
<GeneratePathProperty></GeneratePathProperty>
</PackageReference>
</ItemGroup>
</Project>
Loading