Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
- name: setup-dotnet
uses: actions/setup-dotnet@v3
# build it, test it, pack it
- name: Run dotnet build
- name: Run dotnet build (release)
run: ./build.cmd

# deploy:
Expand Down
30 changes: 3 additions & 27 deletions .github/workflows/main.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: ci
name: Build main (release)

on:
push:
Expand All @@ -19,7 +19,7 @@ jobs:
- name: setup-dotnet
uses: actions/setup-dotnet@v3
# build it, test it, pack it
- name: Run dotnet build
- name: Run dotnet build (release)
run: ./build.cmd

test-release:
Expand All @@ -36,7 +36,7 @@ jobs:
uses: actions/setup-dotnet@v3
# build it, test it, pack it
- name: Run dotnet test - release
run: dotnet test -c Release --blame-hang-timeout 15000ms --logger "trx;LogFileName=test-results-release.trx" --logger "console;verbosity=detailed" .\src\FSharpy.TaskSeq.Test\FSharpy.TaskSeq.Test.fsproj
run: ./build.cmd ci -release
- name: Publish test results - release
uses: dorny/test-reporter@v1
if: always()
Expand All @@ -46,30 +46,6 @@ jobs:
path: ./src/FSharpy.TaskSeq.Test/TestResults/test-results-release.trx
reporter: dotnet-trx

test-debug:
name: Test Debug Build
runs-on: windows-latest
steps:
# checkout the code
- name: checkout-code
uses: actions/checkout@v3
with:
fetch-depth: 0
# setup dotnet based on global.json
- name: setup-dotnet
uses: actions/setup-dotnet@v3
# build it, test it, pack it
- name: Run dotnet test - debug
run: dotnet test -c Debug --blame-hang-timeout 15000ms --logger "trx;LogFileName=test-results-debug.trx" --logger "console;verbosity=detailed" .\src\FSharpy.TaskSeq.Test\FSharpy.TaskSeq.Test.fsproj
- name: Publish test results - debug
uses: dorny/test-reporter@v1
if: always()
with:
name: Report debug tests
# this path glob pattern requires forward slashes!
path: ./src/FSharpy.TaskSeq.Test/TestResults/test-results-debug.trx
reporter: dotnet-trx

# deploy:
# name: deploy
# runs-on: ubuntu-latest
Expand Down
9 changes: 4 additions & 5 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ jobs:
# setup dotnet based on global.json
- name: setup-dotnet
uses: actions/setup-dotnet@v3
# build it, test it, pack it
# build it, test it
- name: Run dotnet test - release
run: dotnet test -c Release --blame-hang-timeout 15000ms --logger "trx;LogFileName=test-results-release.trx" --logger "console;verbosity=detailed" .\src\FSharpy.TaskSeq.Test\FSharpy.TaskSeq.Test.fsproj
run: ./build.cmd ci -release
- name: Publish test results - release
uses: dorny/test-reporter@v1
if: always()
Expand All @@ -39,9 +39,9 @@ jobs:
# setup dotnet based on global.json
- name: setup-dotnet
uses: actions/setup-dotnet@v3
# build it, test it, pack it
# build it, test it
- name: Run dotnet test - debug
run: dotnet test -c Debug --blame-hang-timeout 15000ms --logger "trx;LogFileName=test-results-debug.trx" --logger "console;verbosity=detailed" .\src\FSharpy.TaskSeq.Test\FSharpy.TaskSeq.Test.fsproj
run: ./build.cmd ci -debug
- name: Publish test results - debug
uses: dorny/test-reporter@v1
if: always()
Expand All @@ -50,4 +50,3 @@ jobs:
# this path glob pattern requires forward slashes!
path: ./src/FSharpy.TaskSeq.Test/TestResults/test-results-debug.trx
reporter: dotnet-trx

88 changes: 82 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,76 @@
[![build](https://github.com/abelbraaksma/TaskSeq/actions/workflows/main.yaml/badge.svg)](https://github.com/abelbraaksma/TaskSeq/actions/workflows/main.yaml)
[![test](https://github.com/abelbraaksma/TaskSeq/actions/workflows/test.yaml/badge.svg)](https://github.com/abelbraaksma/TaskSeq/actions/workflows/test.yaml)
[![build][buildstatus_img]][buildstatus]
[![test][teststatus_img]][teststatus]

# TaskSeq
An implementation [`IAsyncEnumerable<'T>`](https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.iasyncenumerable-1?view=net-7.0) as a `taskSeq` CE for F# with accompanying `TaskSeq` module.
An implementation [`IAsyncEnumerable<'T>`][3] as a `taskSeq` CE for F# with accompanying `TaskSeq` module.

The `IAsyncEnumerable` interface was added to .NET in `.NET Core 3.0` and is part of `.NET Standard 2.1`. The main use-case was for iterative asynchronous enumeration over some resource. For instance, an event stream or a REST API interface with pagination, where each page is a [`MoveNextAsync`](https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.iasyncenumerator-1.movenextasync?view=net-7.0) call on the [`IAsyncEnumerator<'T>`](https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.iasyncenumerator-1?view=net-7.0) given by a call to [`GetAsyncEnumerator()`](https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.iasyncenumerable-1.getasyncenumerator?view=net-7.0). It has been relatively challenging to work properly with this type and dealing with each step being asynchronous, and the enumerator implementing [`IAsyncDisposable`](https://learn.microsoft.com/en-us/dotnet/api/system.iasyncdisposable?view=net-7.0) as well, which requires careful handling.
The `IAsyncEnumerable` interface was added to .NET in `.NET Core 3.0` and is part of `.NET Standard 2.1`. The main use-case was for iterative asynchronous enumeration over some resource. For instance, an event stream or a REST API interface with pagination, where each page is a [`MoveNextAsync`][4] call on the [`IAsyncEnumerator<'T>`][5] given by a call to [`GetAsyncEnumerator()`][6]. It has been relatively challenging to work properly with this type and dealing with each step being asynchronous, and the enumerator implementing [`IAsyncDisposable`][7] as well, which requires careful handling.

A good C#-based introduction on `IAsyncEnumerable` [can be found in this blog][8]. Another resource is [this MSDN article shortly after its introductiono][9].

## Building & testing

TLDR: just run `build`. Or load the `sln` file in Visual Studio or VS Code and compile.

### Prerequisites

* .NET 6 or .NET 7 Preview
* F# 6.0 compiler
* To use `build.cmd`, the `dotnet` command must be accessible from your path.

Just checkout this repo locally. Then, from the root of the repo, you can do:

### Build the solution

```
build [release|debug]
```

### Run the tests

```
build test [release|debug]
```

By default, all tests are output to the console. If you don't want that, you can use `--logger console;verbosity=summary`.
Furthermore, no TRX file is generated and the `--blame-xxx` flags aren't set.

### Run the CI command

```
build ci [release|debug]
```

This will run `dotnet test` with the `--blame-xxx` settings enabled to [prevent hanging tests][1] caused by
an [xUnit runner bug][2].

### Advanced

You can pass any additional options that are valid for `dotnet test` and `dotnet build` respectively. However,
these cannot be the very first argument, so you should either use `build build --myadditionalOptions fizz buzz`, or
just specify the build-kind, i.e. this is fine:

```
build debug --verbosity detailed
build test --logger console;verbosity=summary
```

At this moment, additional options cannot have quotes in them.

Command modifiers, like `release` and `debug`, can be specified with `-` or `/` if you so prefer: `dotnet build /release`.

### Get help (duh!)

```
build help
```

For more info, see this PR: https://github.com/abelbraaksma/TaskSeq/pull/29.

A good C#-based introduction on `IAsyncEnumerable` [can be found in this blog](https://stu.dev/iasyncenumerable-introduction/). Another resource is [this MSDN article shortly after its introductiono](https://learn.microsoft.com/en-us/archive/msdn-magazine/2019/november/csharp-iterating-with-async-enumerables-in-csharp-8).

## In progress!!!

It's based on [Don Symes `taskSeq.fs`](https://github.com/dotnet/fsharp/blob/d5312aae8aad650f0043f055bb14c3aa8117e12e/tests/benchmarks/CompiledCodeBenchmarks/TaskPerf/TaskPerf/taskSeq.fs)
It's based on [Don Symes `taskSeq.fs`][10]
but expanded with useful utility functions and a few extra binding overloads.

## Short-term feature planning
Expand Down Expand Up @@ -307,3 +367,19 @@ module TaskSeq =
val foldAsync: folder: ('State -> 'T -> #Task<'State>) -> state: 'State -> taskSeq: taskSeq<'T> -> Task<'State>

```

[buildstatus]: https://github.com/abelbraaksma/TaskSeq/actions/workflows/main.yaml
[buildstatus_img]: https://github.com/abelbraaksma/TaskSeq/actions/workflows/main.yaml/badge.svg
[teststatus]: https://github.com/abelbraaksma/TaskSeq/actions/workflows/test.yaml
[teststatus_img]: https://github.com/abelbraaksma/TaskSeq/actions/workflows/test.yaml/badge.svg

[1]: https://github.com/abelbraaksma/TaskSeq/issues/25
[2]: https://github.com/xunit/xunit/issues/2587
[3]: https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.iasyncenumerable-1?view=net-7.0
[4]: https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.iasyncenumerator-1.movenextasync?view=net-7.0
[5]: https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.iasyncenumerator-1?view=net-7.0
[6]: https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.iasyncenumerable-1.getasyncenumerator?view=net-7.0
[7]: https://learn.microsoft.com/en-us/dotnet/api/system.iasyncdisposable?view=net-7.0
[8]: https://stu.dev/iasyncenumerable-introduction/
[9]: https://learn.microsoft.com/en-us/archive/msdn-magazine/2019/november/csharp-iterating-with-async-enumerables-in-csharp-8
[10]: https://github.com/dotnet/fsharp/blob/d5312aae8aad650f0043f055bb14c3aa8117e12e/tests/benchmarks/CompiledCodeBenchmarks/TaskPerf/TaskPerf/taskSeq.fs
189 changes: 187 additions & 2 deletions build.cmd
Original file line number Diff line number Diff line change
@@ -1,4 +1,189 @@
echo Restoring dotnet tools...
@ECHO OFF

REM Make environment variables local to the batch script
SETLOCAL

REM Default local parameters (BUILD_MODE must remain empty, otherwise, 'help' doesn't work)
SET BUILD_CONFIG=Release
SET BUILD_MODE=

SET DOTNET_TEST_ARGS=
SET DOTNET_TEST_PROJECT_LOCATION=

SET DOTNET_CI_ARGS=--blame-hang-timeout 60000ms --logger "console;verbosity=detailed"
SET DOTNET_TEST_ARGS=--logger "console;verbosity=detailed"
SET DOTNET_TEST_PROJECT_LOCATION=".\src\FSharpy.TaskSeq.Test\FSharpy.TaskSeq.Test.fsproj"

REM This is used to get a 'rest of arguments' list, which allows passing
REM other arguments to the dotnet build and test commands
SET REST_ARGS=%*

:parseArgs
IF "%~1"=="build" (
SET BUILD_MODE=build
REM Drop 'build' from the remaining args
CALL :shiftArg %REST_ARGS%

) ELSE IF "%~1"=="test" (
SET BUILD_MODE=test
REM Drop 'test' from the remaining args
CALL :shiftArg %REST_ARGS%

) ELSE IF "%~1"=="ci" (
SET BUILD_MODE=ci
REM Drop 'ci' from the remaining args
CALL :shiftArg %REST_ARGS%

) ELSE IF "%~1"=="help" (
GOTO :showHelp

) ELSE IF "%~1"=="/help" (
GOTO :showHelp

) ELSE IF "%~1"=="-help" (
GOTO :showHelp

) ELSE IF "%~1"=="" (
REM No args, default: build
SET BUILD_MODE=build
SET BUILD_CONFIG=release
)

CALL :tryBuildConfig %REST_ARGS%
ECHO Additional arguments: %REST_ARGS%

REM Main branching starts here
IF "%BUILD_MODE%"=="build" GOTO :runBuild
IF "%BUILD_MODE%"=="test" GOTO :runTest
IF "%BUILD_MODE%"=="ci" GOTO :runCi


REM Something wrong, we don't recognize the given arguments
REM Display help:

ECHO Argument not recognized

:showHelp
ECHO.
ECHO Available options are:
ECHO.
ECHO build Run 'dotnet build' (default if omitted)
ECHO test Run 'dotnet test' with default configuration and no CI logging.
ECHO ci Run 'dotnet test' with CI configuration and TRX logging.
ECHO.
ECHO Optionally combined with:
ECHO.
ECHO release Build release configuration (default).
ECHO debug Build debug configuration.
ECHO.
ECHO Any arguments that follow the special arguments will be passed on to 'dotnet test' or 'dotnet build'
ECHO Such user-supplied arguments can only be given when one of the above specific commands is used.
ECHO
ECHO Optional arguments may be given with a leading '/' or '-', if so preferred.
ECHO.
ECHO Examples:
ECHO.
ECHO Run default build (release):
ECHO build
ECHO.
ECHO Run debug build:
ECHO build debug
ECHO.
ECHO Run debug build with detailed verbosity:
ECHO build debug --verbosity detailed
ECHO.
ECHO Run the tests in default CI configuration
ECHO build ci
ECHO.
ECHO Run the tests as in CI, but with the Debug configuration
ECHO build ci -debug
ECHO.
ECHO Run the tests without TRX logging
ECHO build test -release
ECHO.
GOTO :EOF

REM Normal building
:runBuild
SET BUILD_COMMAND=dotnet build src/FSharpy.TaskSeq.sln -c %BUILD_CONFIG% %REST_ARGS%
ECHO Building for %BUILD_CONFIG% configuration...
ECHO.
ECHO Executing:
ECHO %BUILD_COMMAND%
ECHO.
ECHO Restoring dotnet tools...
dotnet tool restore
%BUILD_COMMAND%
GOTO :EOF

REM Testing
:runTest
SET TEST_COMMAND=dotnet test -c %BUILD_CONFIG% %DOTNET_TEST_ARGS% %DOTNET_TEST_PROJECT_LOCATION% %REST_ARGS%
ECHO.
ECHO Testing: %BUILD_CONFIG% configuration...
ECHO.
ECHO Restoring dotnet tools...
dotnet tool restore

ECHO Executing:
ECHO %TEST_COMMAND%
%TEST_COMMAND%
GOTO :EOF

REM Continuous integration
:runCi
SET TRX_LOGGER=--logger "trx;LogFileName=test-results-%BUILD_CONFIG%.trx"
SET CI_COMMAND=dotnet test -c %BUILD_CONFIG% %DOTNET_CI_ARGS% %DOTNET_TEST_PROJECT_LOCATION% %TRX_LOGGER% %REST_ARGS%
ECHO.
ECHO Continuous integration: %BUILD_CONFIG% configuration...
ECHO.
ECHO Restoring dotnet tools...
dotnet tool restore

dotnet build src/FSharpy.TaskSeq.sln -c Release
ECHO Executing:
ECHO %CI_COMMAND%
%CI_COMMAND%
GOTO :EOF


REM Callable label, will resume after 'CALL' line
:tryBuildConfig
IF "%~1"=="release" (
SET BUILD_CONFIG=release
CALL :shiftArg %REST_ARGS%
)
IF "%~1"=="-release" (
SET BUILD_CONFIG=release
CALL :shiftArg %REST_ARGS%
)
IF "%~1"=="/release" (
SET BUILD_CONFIG=release
CALL :shiftArg %REST_ARGS%
)
IF "%~1"=="debug" (
SET BUILD_CONFIG=debug
CALL :shiftArg %REST_ARGS%
)
IF "%~1"=="-debug" (
SET BUILD_CONFIG=debug
CALL :shiftArg %REST_ARGS%
)
IF "%~1"=="/debug" (
SET BUILD_CONFIG=debug
CALL :shiftArg %REST_ARGS%
)
GOTO :EOF

REM Callable label, will resume after 'CALL' line
:shiftArg
REM WARNING!!!
REM If called from inside an IF-statement, it will NOT keep the resulting
REM variable %REST_ARGS%, until execution gets OUTSIDE of the IF-block

REM Do not call 'SHIFT' here, as we do it manually
REM Here, '%*' means the arguments given in the CALL command to this label
SET REST_ARGS=%*

REM Shift by stripping until and including the first argument
IF NOT "%REST_ARGS%"=="" CALL SET REST_ARGS=%%REST_ARGS:*%1=%%
GOTO :EOF
1 change: 1 addition & 0 deletions src/FSharpy.TaskSeq.sln
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B252135E-C676-4542-8B72-412DF1B9487C}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
..\build.cmd = ..\build.cmd
..\README.md = ..\README.md
EndProjectSection
EndProject
Expand Down
Loading