-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Homework7 #7
Homework7 #7
Changes from 17 commits
48a3bd3
4dd98d2
c2d1807
fe8ec95
cc1b46f
bb19904
1e4da4f
385efc2
8196111
487d075
921baa2
e4abb33
2f6b344
b69666a
9b32b0c
5d7258f
a2eaec4
1a956da
83eb923
a9bf8f7
5ad0b6c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
| ||
Microsoft Visual Studio Solution File, Format Version 12.00 | ||
# Visual Studio Version 17 | ||
VisualStudioVersion = 17.0.31903.59 | ||
MinimumVisualStudioVersion = 10.0.40219.1 | ||
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "MiniCrawler", "MiniCrawler\MiniCrawler.fsproj", "{7A2FFF58-1A54-4E4A-A5A7-C0C06E345050}" | ||
EndProject | ||
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "MiniCrawler.Tests", "MiniCrawler.Tests\MiniCrawler.Tests.fsproj", "{541551EE-AF15-4DCC-848D-36EDF6A07DD1}" | ||
EndProject | ||
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Lazy", "Lazy\Lazy.fsproj", "{5310B6BA-3D02-4A8D-A067-A9452C046FC9}" | ||
EndProject | ||
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Lazy.Tests", "Lazy.Tests\Lazy.Tests.fsproj", "{C21915B1-5326-48A3-B3B1-7C5E98736BCD}" | ||
EndProject | ||
Global | ||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||
Debug|Any CPU = Debug|Any CPU | ||
Release|Any CPU = Release|Any CPU | ||
EndGlobalSection | ||
GlobalSection(SolutionProperties) = preSolution | ||
HideSolutionNode = FALSE | ||
EndGlobalSection | ||
GlobalSection(ProjectConfigurationPlatforms) = postSolution | ||
{7A2FFF58-1A54-4E4A-A5A7-C0C06E345050}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
{7A2FFF58-1A54-4E4A-A5A7-C0C06E345050}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{7A2FFF58-1A54-4E4A-A5A7-C0C06E345050}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{7A2FFF58-1A54-4E4A-A5A7-C0C06E345050}.Release|Any CPU.Build.0 = Release|Any CPU | ||
{541551EE-AF15-4DCC-848D-36EDF6A07DD1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
{541551EE-AF15-4DCC-848D-36EDF6A07DD1}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{541551EE-AF15-4DCC-848D-36EDF6A07DD1}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{541551EE-AF15-4DCC-848D-36EDF6A07DD1}.Release|Any CPU.Build.0 = Release|Any CPU | ||
{5310B6BA-3D02-4A8D-A067-A9452C046FC9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
{5310B6BA-3D02-4A8D-A067-A9452C046FC9}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{5310B6BA-3D02-4A8D-A067-A9452C046FC9}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{5310B6BA-3D02-4A8D-A067-A9452C046FC9}.Release|Any CPU.Build.0 = Release|Any CPU | ||
{C21915B1-5326-48A3-B3B1-7C5E98736BCD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
{C21915B1-5326-48A3-B3B1-7C5E98736BCD}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{C21915B1-5326-48A3-B3B1-7C5E98736BCD}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{C21915B1-5326-48A3-B3B1-7C5E98736BCD}.Release|Any CPU.Build.0 = Release|Any CPU | ||
EndGlobalSection | ||
EndGlobal |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
<PropertyGroup> | ||
<TargetFramework>net8.0</TargetFramework> | ||
<IsPackable>false</IsPackable> | ||
<GenerateProgramFile>false</GenerateProgramFile> | ||
<IsTestProject>true</IsTestProject> | ||
</PropertyGroup> | ||
<ItemGroup> | ||
<Compile Include="Tests.fs" /> | ||
<Compile Include="Program.fs" /> | ||
</ItemGroup> | ||
<ItemGroup> | ||
<PackageReference Include="FsUnit" Version="6.0.0" /> | ||
<PackageReference Include="FsCheck" Version="2.16.6" /> | ||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" /> | ||
<PackageReference Include="NUnit" Version="4.1.0" /> | ||
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" /> | ||
<PackageReference Include="NUnit.Analyzers" Version="4.0.1" /> | ||
<PackageReference Include="coverlet.collector" Version="6.0.0" /> | ||
</ItemGroup> | ||
<ItemGroup> | ||
<ProjectReference Include="..\Lazy\Lazy.fsproj" /> | ||
</ItemGroup> | ||
</Project> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
module Program = | ||
|
||
[<EntryPoint>] | ||
let main _ = 0 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
module Lazy.Tests | ||
|
||
open Lazy | ||
open NUnit.Framework | ||
open FsUnit | ||
|
||
let mutable counter = 0 | ||
|
||
let lazyConstructors = | ||
[ (fun f -> SingleThreadedLazy f :> ILazy<obj>) | ||
(fun f -> ThreadSafeLazy f :> ILazy<obj>) | ||
(fun f -> LockFreeLazy f :> ILazy<obj>) ] | ||
|> List.map (fun f -> TestCaseData(f)) | ||
|
||
let multiThreadLazyConstructors = | ||
[ (fun f -> ThreadSafeLazy f :> ILazy<obj>) | ||
(fun f -> LockFreeLazy f :> ILazy<obj>) ] | ||
|> List.map (fun f -> TestCaseData(f)) | ||
|
||
[<TestCaseSource(nameof lazyConstructors)>] | ||
let ``Value should compute only once`` (lazyConstructor: (unit -> obj) -> ILazy<obj>) = | ||
let supplier () = | ||
counter <- counter + 1 | ||
obj () | ||
|
||
counter <- 0 | ||
|
||
let lazyObject = lazyConstructor supplier | ||
let firstCallValue = lazyObject.Get() | ||
let secondCallValue = lazyObject.Get() | ||
|
||
Assert.That(counter, Is.EqualTo(1)) | ||
|
||
|
||
[<TestCaseSource(nameof lazyConstructors)>] | ||
let ``Computed value should be same for several Gets`` (lazyConstructor: (unit -> obj) -> ILazy<obj>) = | ||
let supplier () = obj () | ||
|
||
let lazyObject = lazyConstructor supplier | ||
let firstCallValue = lazyObject.Get() | ||
let secondCallValue = lazyObject.Get() | ||
|
||
Assert.That(firstCallValue, Is.EqualTo(secondCallValue)) | ||
|
||
|
||
[<TestCaseSource(nameof lazyConstructors)>] | ||
let ``Exception in supplier should be thrown`` (lazyConstructor: (unit -> obj) -> ILazy<obj>) = | ||
let lazyObject = lazyConstructor (fun () -> raise (System.Exception())) | ||
Assert.Throws<System.Exception>(fun () -> lazyObject.Get() |> ignore) |> ignore | ||
|
||
|
||
[<TestCaseSource(nameof multiThreadLazyConstructors)>] | ||
let ``Multithread lazies test`` (lazyConstructor: (unit -> obj) -> ILazy<obj>) = | ||
let supplier () = obj () | ||
|
||
let lazyObject = lazyConstructor supplier | ||
let amountOfThreads = 8 | ||
|
||
let tasksArray = | ||
Seq.init amountOfThreads (fun _ -> async { return lazyObject.Get() }) | ||
|
||
tasksArray | ||
|> Async.Parallel | ||
|> Async.RunSynchronously | ||
|> Seq.distinct | ||
|> Seq.length | ||
|> should equal 1 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<OutputType>Library</OutputType> | ||
<TargetFramework>net8.0</TargetFramework> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<Compile Include="Library.fs" /> | ||
</ItemGroup> | ||
|
||
</Project> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
module Lazy | ||
|
||
type ILazy<'a> = | ||
abstract member Get: unit -> 'a | ||
|
||
type SingleThreadedLazy<'a>(supplier: unit -> 'a) = | ||
let mutable value: Result<'a, exn> option = None | ||
|
||
interface ILazy<'a> with | ||
member this.Get() = | ||
match value with | ||
| Some(Ok v) -> v | ||
| Some(Error ex) -> raise ex | ||
| None -> | ||
try | ||
let v = supplier () | ||
value <- Some(Ok v) | ||
v | ||
with ex -> | ||
value <- Some(Error ex) | ||
raise ex | ||
|
||
type ThreadSafeLazy<'a>(supplier: unit -> 'a) = | ||
let mutable value: Result<'a, exn> option = None | ||
let syncObj = obj () | ||
|
||
interface ILazy<'a> with | ||
member this.Get() = | ||
match value with | ||
| Some(Ok v) -> v | ||
| Some(Error ex) -> raise ex | ||
| None -> | ||
lock syncObj (fun () -> | ||
match value with | ||
| Some(Ok v) -> v | ||
| Some(Error ex) -> raise ex | ||
| None -> | ||
try | ||
let v = supplier () | ||
value <- Some(Ok v) | ||
v | ||
with ex -> | ||
value <- Some(Error ex) | ||
raise ex) | ||
|
||
type LockFreeLazy<'a>(supplier: unit -> 'a) = | ||
let mutable value: Result<'a, exn> option = None | ||
|
||
interface ILazy<'a> with | ||
member this.Get() = | ||
match value with | ||
| Some(Ok v) -> v | ||
| Some(Error ex) -> raise ex | ||
| None -> | ||
try | ||
let result = supplier () | ||
|
||
match System.Threading.Interlocked.CompareExchange(&value, Some(Ok result), None) with | ||
| Some(Ok v) -> v | ||
| Some(Error ex) -> raise ex | ||
| None -> result | ||
with ex -> | ||
System.Threading.Interlocked.CompareExchange(&value, Some(Error ex), None) | ||
|> ignore | ||
|
||
raise ex | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. нужно точно так же паттерн-матчить, а не игнорировать результат с выбросом исключения, поскольку может произойти ситуация, когда успешный поток записал значение, а неуспешный не успел записать эксепшен -- получим два разных результата |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
<PropertyGroup> | ||
<TargetFramework>net8.0</TargetFramework> | ||
<IsPackable>false</IsPackable> | ||
<GenerateProgramFile>false</GenerateProgramFile> | ||
<IsTestProject>true</IsTestProject> | ||
</PropertyGroup> | ||
<ItemGroup> | ||
<Compile Include="Tests.fs" /> | ||
<Compile Include="Program.fs" /> | ||
</ItemGroup> | ||
<ItemGroup> | ||
<PackageReference Include="FsUnit" Version="6.0.0" /> | ||
<PackageReference Include="FsCheck" Version="2.16.6" /> | ||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" /> | ||
<PackageReference Include="NUnit" Version="4.1.0" /> | ||
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" /> | ||
<PackageReference Include="NUnit.Analyzers" Version="4.0.1" /> | ||
<PackageReference Include="coverlet.collector" Version="6.0.0" /> | ||
</ItemGroup> | ||
<ItemGroup> | ||
<ProjectReference Include="..\MiniCrawler\MiniCrawler.fsproj" /> | ||
</ItemGroup> | ||
</Project> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
module Program = | ||
|
||
[<EntryPoint>] | ||
let main _ = 0 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
module MiniCrawler.Tests | ||
|
||
open MiniCrawler | ||
open FsUnit | ||
open NUnit.Framework | ||
|
||
[<Test>] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. нужны тесты, которые проверят, что в случае исключения при загрузке одной из страниц, для другой спешно выполнится подсчет размера let mock = Mock<HttClient>()
mock.Setup(fun client -> client.GetStringAsync("hwproj.ru"))
.ReturnsAsync("<html>нормальная ссылка / битая ссылка</html>");
downloadPages "hwproj.ru" mock.Object
|
||
let ``extractLinks test`` () = | ||
let url = "https://example.com" | ||
|
||
async { | ||
let! htmlPage = downloadPageAsync url | ||
let actualLinks = extractLinks htmlPage | ||
let expectedLinks = [ "https://www.iana.org/domains/example" ] | ||
Assert.AreEqual(expectedLinks, actualLinks) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
module MiniCrawler | ||
|
||
open System.Net.Http | ||
open System.Text.RegularExpressions | ||
|
||
let downloadPageAsync (url: string) = | ||
async { | ||
try | ||
use client = new HttpClient() | ||
let! html = client.GetStringAsync(url) |> Async.AwaitTask | ||
return html | ||
with ex -> | ||
printfn "Failed to download %s: %s" url ex.Message | ||
return "" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. можно было вернуть None/Result link, Some/None length |
||
} | ||
|
||
let extractLinks (html: string) = | ||
let pattern = @"<a\s+(?:[^>]*?\s+)?href=([""'])(http[^""']+)\1" | ||
let matches = Regex.Matches(html, pattern) | ||
[ for m in matches -> m.Groups.[2].Value ] | ||
|
||
let downloadPages (url: string) = | ||
async { | ||
let! mainPageHtml = downloadPageAsync url | ||
let linksFromMainPage = extractLinks mainPageHtml | ||
|
||
let downloadTasks = | ||
linksFromMainPage | ||
|> List.map (fun link -> | ||
async { | ||
let! html = downloadPageAsync link | ||
return (link, html.Length) | ||
}) | ||
|
||
let! results = Async.Parallel downloadTasks | ||
return results |> List.ofArray | ||
} | ||
|
||
let printSizes (results: (string * int) list) = | ||
results |> List.iter (fun (link, size) -> printfn "%s — %d" link size) | ||
|
||
let downloadAndPrintSizes (url: string) = | ||
async { | ||
let! results = downloadPages url | ||
printSizes results | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<OutputType>Library</OutputType> | ||
<TargetFramework>net8.0</TargetFramework> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<Compile Include="Library.fs" /> | ||
</ItemGroup> | ||
|
||
</Project> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
нужен ещё один тест, который внутри Supplier будет вызывать Interlocked.Increment &counter, чтобы проверить, что supplier у lock-реализации в многопоточной среде вызывается 1 раз
ну и с эксепшенами в многопоточной среде