diff --git a/.gitignore b/.gitignore index ee4dfaed9..4bae338f0 100644 --- a/.gitignore +++ b/.gitignore @@ -294,4 +294,5 @@ __pycache__/ # Functions Click functions-extensions/ -.vscode/ \ No newline at end of file +.vscode/ +.ionide/ diff --git a/samples/VSSample.Tests/VSSample.Tests.csproj b/samples/VSSample.Tests/VSSample.Tests.csproj index 3b71780f2..9223265cb 100644 --- a/samples/VSSample.Tests/VSSample.Tests.csproj +++ b/samples/VSSample.Tests/VSSample.Tests.csproj @@ -1,12 +1,12 @@  - netcoreapp2.2 + netcoreapp3.1 false - + diff --git a/samples/csx/extensions.csproj b/samples/csx/extensions.csproj index b9d3f53ee..c37799249 100644 --- a/samples/csx/extensions.csproj +++ b/samples/csx/extensions.csproj @@ -5,7 +5,7 @@ ** - + diff --git a/samples/csx/host.json b/samples/csx/host.json index 22dd1e6fc..eaa477c0e 100644 --- a/samples/csx/host.json +++ b/samples/csx/host.json @@ -1,6 +1,10 @@ { "version": "2.0", "logging": { + "logLevel": { + "DurableTask.AzureStorage": "Warning", + "DurableTask.Core": "Warning" + }, "applicationInsights": { "samplingSettings": { "isEnabled": false diff --git a/samples/entitites-csharp/Chirper/Chirper.Service/Chirper.Service.csproj b/samples/entitites-csharp/Chirper/Chirper.Service/Chirper.Service.csproj index 90e11b156..f0d39269e 100644 --- a/samples/entitites-csharp/Chirper/Chirper.Service/Chirper.Service.csproj +++ b/samples/entitites-csharp/Chirper/Chirper.Service/Chirper.Service.csproj @@ -1,14 +1,14 @@  - netcoreapp2.1 - v2 + netcoreapp3.1 + v3 - - + + diff --git a/samples/entitites-csharp/Chirper/Chirper.Service/host.json b/samples/entitites-csharp/Chirper/Chirper.Service/host.json index f2babe9be..5542fe5cb 100644 --- a/samples/entitites-csharp/Chirper/Chirper.Service/host.json +++ b/samples/entitites-csharp/Chirper/Chirper.Service/host.json @@ -1,6 +1,10 @@ { "version": "2.0", "logging": { + "logLevel": { + "DurableTask.AzureStorage": "Warning", + "DurableTask.Core": "Warning" + }, "applicationInsights": { "samplingSettings": { "isEnabled": false diff --git a/samples/entitites-csharp/RideSharing/RideSharing/RideSharing.csproj b/samples/entitites-csharp/RideSharing/RideSharing/RideSharing.csproj index 90e11b156..6fafa9467 100644 --- a/samples/entitites-csharp/RideSharing/RideSharing/RideSharing.csproj +++ b/samples/entitites-csharp/RideSharing/RideSharing/RideSharing.csproj @@ -7,8 +7,8 @@ - - + + diff --git a/samples/entitites-csharp/RideSharing/RideSharing/host.json b/samples/entitites-csharp/RideSharing/RideSharing/host.json index dbfc204dd..5542fe5cb 100644 --- a/samples/entitites-csharp/RideSharing/RideSharing/host.json +++ b/samples/entitites-csharp/RideSharing/RideSharing/host.json @@ -1,8 +1,12 @@ { "version": "2.0", "logging": { + "logLevel": { + "DurableTask.AzureStorage": "Warning", + "DurableTask.Core": "Warning" + }, "applicationInsights": { - "sampling": { + "samplingSettings": { "isEnabled": false } } diff --git a/samples/fsharp/DurableFSharp.fsproj b/samples/fsharp/DurableFSharp.fsproj index 4f2683401..987951ca3 100644 --- a/samples/fsharp/DurableFSharp.fsproj +++ b/samples/fsharp/DurableFSharp.fsproj @@ -8,10 +8,10 @@ - + - + diff --git a/samples/fsharp/host.json b/samples/fsharp/host.json index d30499d44..9092a6a80 100644 --- a/samples/fsharp/host.json +++ b/samples/fsharp/host.json @@ -1,6 +1,10 @@ { "version": "2.0", "logging": { + "logLevel": { + "DurableTask.AzureStorage": "Warning", + "DurableTask.Core": "Warning" + }, "applicationInsights": { "samplingSettings": { "isEnabled": false diff --git a/samples/javascript/extensions.csproj b/samples/javascript/extensions.csproj index 19f6321f8..8288cb61e 100644 --- a/samples/javascript/extensions.csproj +++ b/samples/javascript/extensions.csproj @@ -5,7 +5,7 @@ ** - + diff --git a/samples/javascript/host.json b/samples/javascript/host.json index 4798c2852..735669e4a 100644 --- a/samples/javascript/host.json +++ b/samples/javascript/host.json @@ -1,6 +1,10 @@ { "version": "2.0", "logging": { + "logLevel": { + "DurableTask.AzureStorage": "Warning", + "DurableTask.Core": "Warning" + }, "applicationInsights": { "samplingSettings": { "isEnabled": false diff --git a/samples/javascript/package-lock.json b/samples/javascript/package-lock.json index c938fb84c..fda4cebb6 100644 --- a/samples/javascript/package-lock.json +++ b/samples/javascript/package-lock.json @@ -5,27 +5,19 @@ "requires": true, "dependencies": { "@azure/functions": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@azure/functions/-/functions-1.0.3.tgz", - "integrity": "sha512-/D+sz6LgWT+A6RRW2zhwlwhKqqDSxL6HCF1Q1lN0iXolD2FfNFZpzrOxGyGYEEXp/5Dtjp12bcRTBhMH1cBi2Q==" + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@azure/functions/-/functions-1.2.2.tgz", + "integrity": "sha512-p/dDHq1sG/iAib+eDY4NxskWHoHW1WFzD85s0SfWxc2wVjJbxB0xz/zBF4s7ymjVgTu+0ceipeBk+tmpnt98oA==" }, "@types/lodash": { - "version": "4.14.144", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.144.tgz", - "integrity": "sha512-ogI4g9W5qIQQUhXAclq6zhqgqNUr7UlFaqDHbch7WLSLeeM/7d3CRaw7GLajxvyFvhJqw4Rpcz5bhoaYtIx6Tg==" - }, - "@types/node": { - "version": "12.12.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.5.tgz", - "integrity": "sha512-KEjODidV4XYUlJBF3XdjSH5FWoMCtO0utnhtdLf1AgeuZLOrRbvmU/gaRCVg7ZaQDjVf3l84egiY0mRNe5xE4A==" + "version": "4.14.160", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.160.tgz", + "integrity": "sha512-aP03BShJoO+WVndoVj/WNcB/YBPt+CIU1mvaao2GRAHy2yg4pT/XS4XnVHEQBjPJGycWf/9seKEO9vopTJGkvA==" }, "@types/uuid": { - "version": "3.4.6", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.6.tgz", - "integrity": "sha512-cCdlC/1kGEZdEglzOieLDYBxHsvEOIg7kp/2FYyVR9Pxakq+Qf/inL3RKQ+PA8gOlI/NnL+fXmQH12nwcGzsHw==", - "requires": { - "@types/node": "*" - } + "version": "3.4.9", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.9.tgz", + "integrity": "sha512-XDwyIlt/47l2kWLTzw/mtrpLdB+GPSskR2n/PIcPn+VYhVO77rGhRncIR5GPU0KRzXuqkDO+J5qqrG0Y8P6jzQ==" }, "@types/validator": { "version": "9.4.4", @@ -102,19 +94,11 @@ "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" }, "axios": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.0.tgz", - "integrity": "sha512-1uvKqKQta3KBxIz14F2v06AEHZ/dIoeKfbTRkK1E5oqjDnuEerLmYTgJB5AiQZHJcljpg1TuRzdjDR06qNk0DQ==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", + "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==", "requires": { - "follow-redirects": "1.5.10", - "is-buffer": "^2.0.2" - }, - "dependencies": { - "is-buffer": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", - "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==" - } + "follow-redirects": "1.5.10" } }, "azure-storage": { @@ -390,9 +374,9 @@ "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, "durable-functions": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/durable-functions/-/durable-functions-1.3.1.tgz", - "integrity": "sha512-C1sXUfYsF+ESQjJpGSq03MWqDn03hXJJqAHQiEJwc3xgDcbZwed9cTNC07Mh0H+6SqAVjlxktdGDyoSNmfMxwA==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/durable-functions/-/durable-functions-1.4.3.tgz", + "integrity": "sha512-MJMnqpHmAuZt+IK6uTij7DE+yzMa+++YPhdb1FOIwm+AeBI/R4CYZnnsmOo6DnrmOIfnF0XBewL0nEhx23se8g==", "requires": { "@azure/functions": "^1.0.2-beta2", "@types/lodash": "^4.14.119", @@ -403,6 +387,7 @@ "debug": "~2.6.9", "lodash": "^4.17.15", "rimraf": "~2.5.4", + "typedoc": "^0.17.1", "uuid": "~3.3.2", "validator": "~10.8.0" }, @@ -620,6 +605,23 @@ "map-cache": "^0.2.2" } }, + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "dependencies": { + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" + } + } + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -639,9 +641,9 @@ } }, "glob": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.5.tgz", - "integrity": "sha512-J9dlskqUXK1OeTOYBEn5s8aMukWMwWfs+rPTn/jn50Ux4MNXVhubL1wu/j2t+H4NVI+cXEcCaYellqaPVGXNqQ==", + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -661,6 +663,25 @@ "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=" }, + "handlebars": { + "version": "4.7.6", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.6.tgz", + "integrity": "sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA==", + "requires": { + "minimist": "^1.2.5", + "neo-async": "^2.6.0", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4", + "wordwrap": "^1.0.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -713,6 +734,11 @@ "safe-buffer": "^5.0.1" } }, + "highlight.js": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.1.2.tgz", + "integrity": "sha512-Q39v/Mn5mfBlMff9r+zzA+gWxRsCRKwEMvYTiisLr/XUiFI/4puWt0Ojdko3R3JCNWGdOWaA5g/Yxqa23kC5AA==" + }, "http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", @@ -737,6 +763,11 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, + "interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==" + }, "is-accessor-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", @@ -879,6 +910,14 @@ "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "requires": { + "graceful-fs": "^4.1.6" + } + }, "jsonparse": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.2.0.tgz", @@ -896,14 +935,19 @@ } }, "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" }, "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" + }, + "lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==" }, "map-cache": { "version": "0.2.2", @@ -918,9 +962,14 @@ "object-visit": "^1.0.0" } }, + "marked": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-1.0.0.tgz", + "integrity": "sha512-Wo+L1pWTVibfrSr+TTtMuiMfNzmZWiOPeO7rZsQUY5bgsxpHesBEcIWJloWVTFnrMXnf/TL30eTFSGJddmQAng==" + }, "md5.js": { "version": "1.3.4", - "resolved": "http://registry.npmjs.org/md5.js/-/md5.js-1.3.4.tgz", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.4.tgz", "integrity": "sha1-6b296UogpawYsENA/Fdk1bCdkB0=", "requires": { "hash-base": "^3.0.0", @@ -968,6 +1017,11 @@ "brace-expansion": "^1.1.7" } }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, "mixin-deep": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", @@ -1015,6 +1069,11 @@ "to-regex": "^3.0.1" } }, + "neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" + }, "oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", @@ -1082,6 +1141,11 @@ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" + }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -1097,6 +1161,11 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==" + }, "psl": { "version": "1.1.29", "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz", @@ -1114,7 +1183,7 @@ }, "readable-stream": { "version": "2.0.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", "requires": { "core-util-is": "~1.0.0", @@ -1135,6 +1204,14 @@ "readable-stream": "^2.0.2" } }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "requires": { + "resolve": "^1.1.6" + } + }, "regex-not": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", @@ -1199,6 +1276,14 @@ "tough-cookie": ">=2.3.3" } }, + "resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "requires": { + "path-parse": "^1.0.6" + } + }, "resolve-url": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", @@ -1224,7 +1309,7 @@ }, "safe-regex": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", "requires": { "ret": "~0.1.10" @@ -1237,7 +1322,7 @@ }, "sax": { "version": "0.5.8", - "resolved": "http://registry.npmjs.org/sax/-/sax-0.5.8.tgz", + "resolved": "https://registry.npmjs.org/sax/-/sax-0.5.8.tgz", "integrity": "sha1-1HLbIo6zMcJQaw6MFVJK25OdEsE=" }, "seedrandom": { @@ -1266,6 +1351,16 @@ } } }, + "shelljs": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.4.tgz", + "integrity": "sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ==", + "requires": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + } + }, "snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", @@ -1435,7 +1530,7 @@ }, "string_decoder": { "version": "0.10.31", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" }, "to-object-path": { @@ -1505,6 +1600,42 @@ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" }, + "typedoc": { + "version": "0.17.8", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.17.8.tgz", + "integrity": "sha512-/OyrHCJ8jtzu+QZ+771YaxQ9s4g5Z3XsQE3Ma7q+BL392xxBn4UMvvCdVnqKC2T/dz03/VXSLVKOP3lHmDdc/w==", + "requires": { + "fs-extra": "^8.1.0", + "handlebars": "^4.7.6", + "highlight.js": "^10.0.0", + "lodash": "^4.17.15", + "lunr": "^2.3.8", + "marked": "1.0.0", + "minimatch": "^3.0.0", + "progress": "^2.0.3", + "shelljs": "^0.8.4", + "typedoc-default-themes": "^0.10.2" + } + }, + "typedoc-default-themes": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/typedoc-default-themes/-/typedoc-default-themes-0.10.2.tgz", + "integrity": "sha512-zo09yRj+xwLFE3hyhJeVHWRSPuKEIAsFK5r2u47KL/HBKqpwdUSanoaz5L34IKiSATFrjG5ywmIu98hPVMfxZg==", + "requires": { + "lunr": "^2.3.8" + } + }, + "typescript": { + "version": "3.9.7", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz", + "integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==" + }, + "uglify-js": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.10.2.tgz", + "integrity": "sha512-GXCYNwqoo0MbLARghYjxVBxDCnU0tLqN7IPLdHHbibCb1NI5zBkU2EPcy/GaVxc0BtTjqyGXJCINe6JMR2Dpow==", + "optional": true + }, "underscore": { "version": "1.8.3", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", @@ -1542,6 +1673,11 @@ } } }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + }, "unset-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", @@ -1608,7 +1744,7 @@ }, "validator": { "version": "9.4.1", - "resolved": "http://registry.npmjs.org/validator/-/validator-9.4.1.tgz", + "resolved": "https://registry.npmjs.org/validator/-/validator-9.4.1.tgz", "integrity": "sha512-YV5KjzvRmSyJ1ee/Dm5UED0G+1L4GZnLN3w6/T+zZm8scVua4sOhYKWTUrKa0H/tMiJyO9QLHMPN+9mB/aMunA==" }, "verror": { @@ -1621,6 +1757,11 @@ "extsprintf": "^1.2.0" } }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=" + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -1636,7 +1777,7 @@ }, "xmlbuilder": { "version": "9.0.7", - "resolved": "http://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" } } diff --git a/samples/javascript/package.json b/samples/javascript/package.json index 77a55cc21..ea6fd1a86 100644 --- a/samples/javascript/package.json +++ b/samples/javascript/package.json @@ -3,14 +3,15 @@ "version": "2.0.0", "description": "Durable Functions sample library for Node.js Azure Functions", "license": "MIT", - "repository": "", + "repository": "https://github.com/Azure/azure-functions-durable-js", "dependencies": { "azure-storage": "^2.10.1", - "durable-functions": "^1.3.1", + "durable-functions": "^1.4.3", "moment": "^2.22.1", "readdirp": "^2.1.0", "request-promise-native": "^1.0.5", "seedrandom": "^2.4.3", + "typescript": "^3.8.3", "uuid": "^3.2.1" } } diff --git a/samples/precompiled/VSSample.csproj b/samples/precompiled/VSSample.csproj index aa3ad49bc..778876c76 100644 --- a/samples/precompiled/VSSample.csproj +++ b/samples/precompiled/VSSample.csproj @@ -1,12 +1,13 @@  - netstandard2.0 - v2 + netcoreapp3.1 + v3 - + + diff --git a/samples/precompiled/host.json b/samples/precompiled/host.json index 28ac2321c..57c9925af 100644 --- a/samples/precompiled/host.json +++ b/samples/precompiled/host.json @@ -1,6 +1,10 @@ { "version": "2.0", "logging": { + "logLevel": { + "DurableTask.AzureStorage": "Warning", + "DurableTask.Core": "Warning" + }, "applicationInsights": { "samplingSettings": { "isEnabled": false diff --git a/samples/precompiled/local.settings.json b/samples/precompiled/local.settings.json index a2ebc46cc..c8e7d8cb9 100644 --- a/samples/precompiled/local.settings.json +++ b/samples/precompiled/local.settings.json @@ -1,6 +1,7 @@ { "IsEncrypted": false, "Values": { + "FUNCTIONS_WORKER_RUNTIME": "dotnet", "AzureWebJobsStorage": "UseDevelopmentStorage=true", "TwilioAccountSid": "", "TwilioAuthToken": "", diff --git a/src/WebJobs.Extensions.DurableTask.Analyzers/ActivityFunctionCall.cs b/src/WebJobs.Extensions.DurableTask.Analyzers/ActivityFunctionCall.cs index 85a54de07..64a8db8fc 100644 --- a/src/WebJobs.Extensions.DurableTask.Analyzers/ActivityFunctionCall.cs +++ b/src/WebJobs.Extensions.DurableTask.Analyzers/ActivityFunctionCall.cs @@ -7,9 +7,9 @@ namespace Microsoft.Azure.WebJobs.Extensions.DurableTask.Analyzers { public class ActivityFunctionCall { - public string Name { get; set; } + public string FunctionName { get; set; } public SyntaxNode NameNode { get; set; } - public SyntaxNode ParameterNode { get; set; } + public SyntaxNode ArgumentNode { get; set; } public SyntaxNode ReturnTypeNode { get; set; } public SyntaxNode InvocationExpression { get; set; } } diff --git a/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/ActivityFunction/ArgumentAnalyzer.cs b/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/ActivityFunction/ArgumentAnalyzer.cs index 71db1975a..de2e567b0 100644 --- a/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/ActivityFunction/ArgumentAnalyzer.cs +++ b/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/ActivityFunction/ArgumentAnalyzer.cs @@ -3,8 +3,8 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; -using System; using System.Collections.Generic; using System.Linq; @@ -24,98 +24,90 @@ public class ArgumentAnalyzer public static readonly DiagnosticDescriptor InputNotUsedRule = new DiagnosticDescriptor(DiagnosticId, Title, InputNotUsedMessageFormat, Category, Severity, isEnabledByDefault: true, description: Description); public static void ReportProblems( - CompilationAnalysisContext context, - SemanticModel semanticModel, - IEnumerable availableFunctions, - IEnumerable calledFunctions) + CompilationAnalysisContext context, + SemanticModel semanticModel, + IEnumerable functionDefinitions, + IEnumerable functionInvocations) { - foreach (var activityInvocation in calledFunctions) + foreach (var invocation in functionInvocations) { - var functionDefinition = availableFunctions.Where(x => x.FunctionName == activityInvocation.Name).FirstOrDefault(); - if (functionDefinition != null) + var definition = functionDefinitions.Where(x => x.FunctionName == invocation.FunctionName).FirstOrDefault(); + if (definition != null) { - var isInvokedWithNonNullInput = TryGetInvocationInputType(semanticModel, activityInvocation, out ITypeSymbol invocationInputType); - var functionDefinitionUsesInput = TryGetDefinitionInputType(semanticModel, functionDefinition, out ITypeSymbol definitionInputType); + var isInvokedWithNonNullInput = TryGetInvocationInputType(semanticModel, invocation, out ITypeSymbol invocationInputType); + var functionDefinitionUsesInput = TryGetDefinitionInputType(semanticModel, definition, out ITypeSymbol definitionInputType); - if (!functionDefinitionUsesInput) + if (isInvokedWithNonNullInput && invocationInputType != null) { - if (isInvokedWithNonNullInput) + if (!functionDefinitionUsesInput) { - var diagnostic = Diagnostic.Create(InputNotUsedRule, activityInvocation.ParameterNode.GetLocation(), activityInvocation.Name); + var diagnostic = Diagnostic.Create(InputNotUsedRule, invocation.ArgumentNode.GetLocation(), invocation.FunctionName); context.ReportDiagnostic(diagnostic); } - } - else if (!IsValidArgumentForDefinition(invocationInputType, definitionInputType)) - { - var invocationTypeName = SyntaxNodeUtils.GetQualifiedTypeName(invocationInputType); - var definitionTypeName = SyntaxNodeUtils.GetQualifiedTypeName(definitionInputType); - - var diagnostic = Diagnostic.Create(MismatchRule, activityInvocation.ParameterNode.GetLocation(), activityInvocation.Name, definitionTypeName, invocationTypeName); + else if (!IsValidArgumentForDefinition(invocationInputType, definitionInputType)) + { + var diagnostic = Diagnostic.Create(MismatchRule, invocation.ArgumentNode.GetLocation(), invocation.FunctionName, definitionInputType.ToString(), invocationInputType.ToString()); - context.ReportDiagnostic(diagnostic); + context.ReportDiagnostic(diagnostic); + } } } } } - private static bool IsValidArgumentForDefinition(ITypeSymbol invocationInputType, ITypeSymbol definitionInputType) - { - return SyntaxNodeUtils.InputMatchesOrCompatibleType(invocationInputType, definitionInputType) - || SyntaxNodeUtils.TypeNodeImplementsOrExtendsType(invocationInputType, definitionInputType.ToString()); - } - private static bool TryGetInvocationInputType(SemanticModel semanticModel, ActivityFunctionCall activityInvocation, out ITypeSymbol invocationInputType) { - var invocationInput = activityInvocation.ParameterNode; - - if (invocationInput == null) + var activityInput = activityInvocation.ArgumentNode; + if (activityInput == null) { invocationInputType = null; return false; } - invocationInputType = SyntaxNodeUtils.GetSyntaxTreeSemanticModel(semanticModel, invocationInput).GetTypeInfo(invocationInput).Type; - - return invocationInputType != null; + return SyntaxNodeUtils.TryGetITypeSymbol(semanticModel, activityInput, out invocationInputType); } private static bool TryGetDefinitionInputType(SemanticModel semanticModel, ActivityFunctionDefinition functionDefinition, out ITypeSymbol definitionInputType) { var definitionInput = functionDefinition.ParameterNode; - if (definitionInput == null) { definitionInputType = null; return false; } - if (FunctionParameterIsContext(semanticModel, definitionInput)) + if (SyntaxNodeUtils.TryGetITypeSymbol(semanticModel, definitionInput, out definitionInputType)) { - if (!TryGetInputFromDurableContextCall(semanticModel, definitionInput, out definitionInput)) + if (SyntaxNodeUtils.IsDurableActivityContext(definitionInputType)) { - definitionInputType = null; - return false; + return TryGetInputTypeFromContext(semanticModel, definitionInput, out definitionInputType); } - } - definitionInputType = SyntaxNodeUtils.GetSyntaxTreeSemanticModel(semanticModel, definitionInput).GetTypeInfo(definitionInput).Type; + return true; + } - return definitionInputType != null; + definitionInputType = null; + return false; } - private static bool FunctionParameterIsContext(SemanticModel semanticModel, SyntaxNode functionInput) + private static bool TryGetInputTypeFromContext(SemanticModel semanticModel, SyntaxNode node, out ITypeSymbol definitionInputType) { - var parameterTypeName = SyntaxNodeUtils.GetSyntaxTreeSemanticModel(semanticModel, functionInput).GetTypeInfo(functionInput).Type.ToString(); + if (TryGetDurableActivityContextExpression(semanticModel, node, out SyntaxNode durableContextExpression)) + { + if (SyntaxNodeUtils.TryGetTypeArgumentIdentifier((MemberAccessExpressionSyntax)durableContextExpression, out SyntaxNode typeArgument)) + { + return SyntaxNodeUtils.TryGetITypeSymbol(semanticModel, typeArgument, out definitionInputType); + } + } - return (parameterTypeName.Equals("Microsoft.Azure.WebJobs.Extensions.DurableTask.IDurableActivityContext") - || parameterTypeName.Equals("Microsoft.Azure.WebJobs.DurableActivityContext") - || parameterTypeName.Equals("Microsoft.Azure.WebJobs.DurableActivityContextBase")); + definitionInputType = null; + return false; } - private static bool TryGetInputFromDurableContextCall(SemanticModel semanticModel, SyntaxNode definitionInput, out SyntaxNode inputFromContext) + private static bool TryGetDurableActivityContextExpression(SemanticModel semanticModel, SyntaxNode node, out SyntaxNode durableContextExpression) { - if (SyntaxNodeUtils.TryGetMethodDeclaration(definitionInput, out SyntaxNode methodDeclaration)) + if (SyntaxNodeUtils.TryGetMethodDeclaration(node, out SyntaxNode methodDeclaration)) { var memberAccessExpressionList = methodDeclaration.DescendantNodes().Where(x => x.IsKind(SyntaxKind.SimpleMemberAccessExpression)); foreach (var memberAccessExpression in memberAccessExpressionList) @@ -123,27 +115,26 @@ private static bool TryGetInputFromDurableContextCall(SemanticModel semanticMode var identifierName = memberAccessExpression.ChildNodes().Where(x => x.IsKind(SyntaxKind.IdentifierName)).FirstOrDefault(); if (identifierName != null) { - var identifierNameType = SyntaxNodeUtils.GetSyntaxTreeSemanticModel(semanticModel, identifierName).GetTypeInfo(identifierName).Type.Name; - if (identifierNameType.Equals("IDurableActivityContext") || identifierNameType.Equals("DurableActivityContext") || identifierNameType.Equals("DurableActivityContextBase")) + if (SyntaxNodeUtils.TryGetITypeSymbol(semanticModel, identifierName, out ITypeSymbol typeSymbol)) { - var genericName = memberAccessExpression.ChildNodes().Where(x => x.IsKind(SyntaxKind.GenericName)).FirstOrDefault(); - if (genericName != null) + if (SyntaxNodeUtils.IsDurableActivityContext(typeSymbol)) { - var typeArgumentList = genericName.ChildNodes().Where(x => x.IsKind(SyntaxKind.TypeArgumentList)).FirstOrDefault(); - if (typeArgumentList != null) - { - inputFromContext = typeArgumentList.ChildNodes().First(); - return true; - } + durableContextExpression = memberAccessExpression; + return true; } } } } } - inputFromContext = null; + durableContextExpression = null; return false; } + + private static bool IsValidArgumentForDefinition(ITypeSymbol invocationInputType, ITypeSymbol definitionInputType) + { + return SyntaxNodeUtils.IsMatchingDerivedOrCompatibleType(invocationInputType, definitionInputType); + } } } diff --git a/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/ActivityFunction/FunctionAnalyzer.cs b/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/ActivityFunction/FunctionAnalyzer.cs index 3c07deb59..ef1907ed8 100644 --- a/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/ActivityFunction/FunctionAnalyzer.cs +++ b/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/ActivityFunction/FunctionAnalyzer.cs @@ -45,35 +45,35 @@ public override void Initialize(AnalysisContext context) private void RegisterAnalyzers(CompilationAnalysisContext context) { - NameAnalyzer.ReportProblems(context, availableFunctions, calledFunctions); + NameAnalyzer.ReportProblems(context, semanticModel, availableFunctions, calledFunctions); ArgumentAnalyzer.ReportProblems(context, semanticModel, availableFunctions, calledFunctions); FunctionReturnTypeAnalyzer.ReportProblems(context, semanticModel, availableFunctions, calledFunctions); } public void FindActivityCall(SyntaxNodeAnalysisContext context) { + SetSemanticModel(context); + var semanticModel = context.SemanticModel; if (context.Node is InvocationExpressionSyntax invocationExpression && SyntaxNodeUtils.IsInsideFunction(semanticModel, invocationExpression) && IsActivityInvocation(invocationExpression)) { - SetSemanticModel(context); - if (!TryGetFunctionNameFromActivityInvocation(invocationExpression, out SyntaxNode functionNameNode, out string functionName)) { //Do not store ActivityFunctionCall if there is no function name return; } - SyntaxNodeUtils.TryGetTypeArgumentNode((MemberAccessExpressionSyntax)invocationExpression.Expression, out SyntaxNode returnTypeNode); + SyntaxNodeUtils.TryGetTypeArgumentIdentifier((MemberAccessExpressionSyntax)invocationExpression.Expression, out SyntaxNode returnTypeNode); TryGetInputNodeFromCallActivityInvocation(invocationExpression, out SyntaxNode inputNode); calledFunctions.Add(new ActivityFunctionCall { - Name = functionName, + FunctionName = functionName, NameNode = functionNameNode, - ParameterNode = inputNode, + ArgumentNode = inputNode, ReturnTypeNode = returnTypeNode, InvocationExpression = invocationExpression }); @@ -93,7 +93,9 @@ private bool IsActivityInvocation(InvocationExpressionSyntax invocationExpressio if (invocationExpression != null && invocationExpression.Expression is MemberAccessExpressionSyntax memberAccessExpression) { var name = memberAccessExpression.Name; - if (name.ToString().StartsWith("CallActivityAsync") || name.ToString().StartsWith("CallActivityWithRetryAsync")) + if (name != null + && (name.ToString().StartsWith("CallActivityAsync") + || name.ToString().StartsWith("CallActivityWithRetryAsync"))) { return true; } @@ -122,14 +124,18 @@ private bool TryGetFunctionNameFromActivityInvocation(InvocationExpressionSyntax private bool TryGetInputNodeFromCallActivityInvocation(InvocationExpressionSyntax invocationExpression, out SyntaxNode inputNode) { - var arguments = invocationExpression.ArgumentList.Arguments; - if (arguments != null && arguments.Count > 1) + var argumentList = invocationExpression.ArgumentList; + if (argumentList != null) { - var lastArgumentNode = arguments.Last(); + var arguments = argumentList.Arguments; + if (arguments != null && arguments.Count > 1) + { + var lastArgumentNode = arguments.Last(); - //An Argument node will always have a child node - inputNode = lastArgumentNode.ChildNodes().First(); - return true; + //An Argument node will always have a child node + inputNode = lastArgumentNode.ChildNodes().First(); + return true; + } } inputNode = null; @@ -138,8 +144,8 @@ private bool TryGetInputNodeFromCallActivityInvocation(InvocationExpressionSynta public void FindActivityFunction(SyntaxNodeAnalysisContext context) { - var attribute = context.Node as AttributeSyntax; - if (SyntaxNodeUtils.IsActivityTriggerAttribute(attribute)) + if (context.Node is AttributeSyntax attribute + && SyntaxNodeUtils.IsActivityTriggerAttribute(attribute)) { if (!SyntaxNodeUtils.TryGetFunctionName(context.SemanticModel, attribute, out string functionName)) { diff --git a/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/ActivityFunction/FunctionReturnTypeAnalyzer.cs b/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/ActivityFunction/FunctionReturnTypeAnalyzer.cs index 7f15ed520..16a5db33a 100644 --- a/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/ActivityFunction/FunctionReturnTypeAnalyzer.cs +++ b/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/ActivityFunction/FunctionReturnTypeAnalyzer.cs @@ -3,7 +3,6 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; -using System; using System.Collections.Generic; using System.Linq; @@ -22,27 +21,25 @@ public class FunctionReturnTypeAnalyzer public static void ReportProblems( - CompilationAnalysisContext context, - SemanticModel semanticModel, - IEnumerable availableFunctions, - IEnumerable calledFunctions) + CompilationAnalysisContext context, + SemanticModel semanticModel, + IEnumerable functionDefinitions, + IEnumerable functionInvocations) { - foreach (var activityInvocation in calledFunctions) + foreach (var invocation in functionInvocations) { - var functionDefinition = availableFunctions.Where(x => x.FunctionName == activityInvocation.Name).FirstOrDefault(); - if (functionDefinition != null && activityInvocation.ReturnTypeNode != null) + var definition = functionDefinitions.Where(x => x.FunctionName == invocation.FunctionName).FirstOrDefault(); + if (definition != null && invocation.ReturnTypeNode != null) { - TryGetInvocationReturnType(semanticModel, activityInvocation, out ITypeSymbol invocationReturnType); - TryGetDefinitionReturnType(semanticModel, functionDefinition, out ITypeSymbol definitionReturnType); - - if (!IsValidReturnTypeForDefinition(invocationReturnType, definitionReturnType)) + if (TryGetInvocationReturnType(semanticModel, invocation, out ITypeSymbol invocationReturnType) + && TryGetDefinitionReturnType(semanticModel, definition, out ITypeSymbol definitionReturnType)) { - var invocationTypeName = SyntaxNodeUtils.GetQualifiedTypeName(invocationReturnType); - var functionTypeName = SyntaxNodeUtils.GetQualifiedTypeName(definitionReturnType); - - var diagnostic = Diagnostic.Create(Rule, activityInvocation.InvocationExpression.GetLocation(), activityInvocation.Name, functionTypeName, invocationTypeName); + if (!IsValidReturnTypeForDefinition(invocationReturnType, definitionReturnType)) + { + var diagnostic = Diagnostic.Create(Rule, invocation.InvocationExpression.GetLocation(), invocation.FunctionName, definitionReturnType.ToString(), invocationReturnType.ToString()); - context.ReportDiagnostic(diagnostic); + context.ReportDiagnostic(diagnostic); + } } } } @@ -55,8 +52,7 @@ private static bool IsValidReturnTypeForDefinition(ITypeSymbol invocationReturnT definitionReturnType = taskTypeArgument; } - return SyntaxNodeUtils.InputMatchesOrCompatibleType(invocationReturnType, definitionReturnType) - || SyntaxNodeUtils.TypeNodeImplementsOrExtendsType(definitionReturnType, invocationReturnType.ToString()); + return SyntaxNodeUtils.IsMatchingDerivedOrCompatibleType(definitionReturnType, invocationReturnType); } private static bool TryGetTaskTypeArgument(ITypeSymbol returnType, out ITypeSymbol taskTypeArgument) @@ -71,18 +67,18 @@ private static bool TryGetTaskTypeArgument(ITypeSymbol returnType, out ITypeSymb return false; } - private static void TryGetInvocationReturnType(SemanticModel semanticModel, ActivityFunctionCall activityInvocation, out ITypeSymbol invocationReturnType) + private static bool TryGetInvocationReturnType(SemanticModel semanticModel, ActivityFunctionCall activityInvocation, out ITypeSymbol invocationReturnType) { var invocationReturnNode = activityInvocation.ReturnTypeNode; - invocationReturnType = SyntaxNodeUtils.GetSyntaxTreeSemanticModel(semanticModel, invocationReturnNode).GetTypeInfo(invocationReturnNode).Type; + return SyntaxNodeUtils.TryGetITypeSymbol(semanticModel, invocationReturnNode, out invocationReturnType); } - private static void TryGetDefinitionReturnType(SemanticModel semanticModel, ActivityFunctionDefinition functionDefinition, out ITypeSymbol definitionReturnType) + private static bool TryGetDefinitionReturnType(SemanticModel semanticModel, ActivityFunctionDefinition functionDefinition, out ITypeSymbol definitionReturnType) { var definitionReturnNode = functionDefinition.ReturnTypeNode; - definitionReturnType = SyntaxNodeUtils.GetSyntaxTreeSemanticModel(semanticModel, definitionReturnNode).GetTypeInfo(definitionReturnNode).Type; + return SyntaxNodeUtils.TryGetITypeSymbol(semanticModel, definitionReturnNode, out definitionReturnType); } } } diff --git a/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/ActivityFunction/NameAnalyzer.cs b/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/ActivityFunction/NameAnalyzer.cs index 41904addc..2c1243a50 100644 --- a/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/ActivityFunction/NameAnalyzer.cs +++ b/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/ActivityFunction/NameAnalyzer.cs @@ -3,6 +3,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; +using System; using System.Collections.Generic; using System.Linq; @@ -28,28 +29,35 @@ private static string GetClosestString(string name, IEnumerable availabl } public static void ReportProblems( - CompilationAnalysisContext context, - IEnumerable availableFunctions, - IEnumerable calledFunctions) + CompilationAnalysisContext context, + SemanticModel semanticModel, + IEnumerable functionDefinitions, + IEnumerable functionInvocations) { - foreach (var activityInvocation in calledFunctions) + foreach (var invocation in functionInvocations) { - if (!availableFunctions.Select(x => x.FunctionName).Contains(activityInvocation.Name)) + // If invocation uses constant and there is no matching function name in function definition, trust the customer for correctness in case they are using true + if (!functionDefinitions.Select(x => x.FunctionName).Contains(invocation.FunctionName) && !IsConstant(semanticModel, invocation.NameNode)) { - if (SyntaxNodeUtils.TryGetClosestString(activityInvocation.Name, availableFunctions.Select(x => x.FunctionName), out string closestName)) + if (SyntaxNodeUtils.TryGetClosestString(invocation.FunctionName, functionDefinitions.Select(x => x.FunctionName), out string closestName)) { - var diagnostic = Diagnostic.Create(CloseRule, activityInvocation.NameNode.GetLocation(), activityInvocation.Name, closestName); + var diagnostic = Diagnostic.Create(CloseRule, invocation.NameNode.GetLocation(), invocation.FunctionName, closestName); context.ReportDiagnostic(diagnostic); } else { - var diagnostic = Diagnostic.Create(MissingRule, activityInvocation.NameNode.GetLocation(), activityInvocation.Name); + var diagnostic = Diagnostic.Create(MissingRule, invocation.NameNode.GetLocation(), invocation.FunctionName); context.ReportDiagnostic(diagnostic); } } } } + + private static bool IsConstant(SemanticModel semanticModel, SyntaxNode nameNode) + { + return SyntaxNodeUtils.TryGetFunctionNameInConstant(semanticModel, nameNode, out string functionName); + } } } diff --git a/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/Entity/ClassNameAnalyzer.cs b/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/Entity/ClassNameAnalyzer.cs index 238eca496..adbaf2208 100644 --- a/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/Entity/ClassNameAnalyzer.cs +++ b/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/Entity/ClassNameAnalyzer.cs @@ -7,7 +7,6 @@ using Microsoft.CodeAnalysis.Diagnostics; using System.Collections.Generic; using System.Collections.Immutable; -using System.Linq; namespace Microsoft.Azure.WebJobs.Extensions.DurableTask.Analyzers { diff --git a/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/Entity/DispatchEntityNameAnalyzer.cs b/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/Entity/DispatchEntityNameAnalyzer.cs index a618dcf7d..e7bb3588b 100644 --- a/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/Entity/DispatchEntityNameAnalyzer.cs +++ b/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/Entity/DispatchEntityNameAnalyzer.cs @@ -67,7 +67,7 @@ private void AnalyzeDispatchAndFindMethodDeclarations(SyntaxNodeAnalysisContext methodDeclarations.Add(methodDeclaration); } - if (SyntaxNodeUtils.TryGetTypeArgumentNode(expression, out SyntaxNode identifierNode)) + if (SyntaxNodeUtils.TryGetTypeArgumentIdentifier(expression, out SyntaxNode identifierNode)) { if (SyntaxNodeUtils.TryGetFunctionName(context.SemanticModel, expression, out string functionName)) { diff --git a/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/EntityInterface/InterfaceAnalyzer.cs b/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/EntityInterface/InterfaceAnalyzer.cs index 5c951c43e..f90cde86b 100644 --- a/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/EntityInterface/InterfaceAnalyzer.cs +++ b/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/EntityInterface/InterfaceAnalyzer.cs @@ -71,36 +71,28 @@ public void FindEntityCalls(SyntaxNodeAnalysisContext context) var name = expression.Name; if (name.ToString().StartsWith("SignalEntityAsync")) { - if (TryGetTypeArgumentList(expression, out SyntaxNode typeArgumentList)) + if (!SyntaxNodeUtils.TryGetTypeArgumentIdentifier(expression, out SyntaxNode typeArgument)) { - if (!TryGetIdentifierName(typeArgumentList, out SyntaxNode identifierName)) - { - var diagnosticWrongType = Diagnostic.Create(Rule, typeArgumentList.GetLocation(), typeArgumentList); - - context.ReportDiagnostic(diagnosticWrongType); - - return; - } - - if (TryFindEntityInterface(identifierName, context, out EntityInterface entityInterface)) - { - entityInterfacesList.Add(entityInterface); - } - else - { - var diagnostic = Diagnostic.Create(Rule, identifierName.GetLocation(), identifierName); + return; + } + + if (TryFindEntityInterface(context, typeArgument, out EntityInterface entityInterface)) + { + entityInterfacesList.Add(entityInterface); + } + else + { + var diagnostic = Diagnostic.Create(Rule, typeArgument.GetLocation(), typeArgument); - context.ReportDiagnostic(diagnostic); - } + context.ReportDiagnostic(diagnostic); } } } } - private bool TryFindEntityInterface(SyntaxNode identifierName, SyntaxNodeAnalysisContext context, out EntityInterface entityInterface) + private bool TryFindEntityInterface(SyntaxNodeAnalysisContext context, SyntaxNode identifierName, out EntityInterface entityInterface) { - var interfaceSymbol = context.SemanticModel.GetSymbolInfo(identifierName, context.CancellationToken).Symbol; - if (interfaceSymbol != null) + if (SyntaxNodeUtils.TryGetISymbol(context.SemanticModel, identifierName, out ISymbol interfaceSymbol)) { var syntaxReference = interfaceSymbol.DeclaringSyntaxReferences.FirstOrDefault(); if (syntaxReference != null) @@ -108,12 +100,9 @@ private bool TryFindEntityInterface(SyntaxNode identifierName, SyntaxNodeAnalysi var declaration = syntaxReference.GetSyntax(context.CancellationToken); if (declaration != null) { - var interfaceKeyword = declaration.ChildTokens().Where(x => x.IsKind(SyntaxKind.InterfaceKeyword)); - if (interfaceKeyword.Any()) + if (IsInterface(declaration)) { - var interfaceType = context.SemanticModel.GetTypeInfo(declaration).Type; - - entityInterface = new EntityInterface { name = identifierName.ToString(), InterfaceDeclaration = declaration, typeSymbol = interfaceType }; + entityInterface = new EntityInterface { Name = identifierName.ToString(), InterfaceDeclaration = declaration }; return true; } } @@ -124,24 +113,10 @@ private bool TryFindEntityInterface(SyntaxNode identifierName, SyntaxNodeAnalysi return false; } - private bool TryGetIdentifierName(SyntaxNode typeArgumentList, out SyntaxNode identifierName) - { - identifierName = typeArgumentList.ChildNodes().Where(x => x.IsKind(SyntaxKind.IdentifierName)).FirstOrDefault(); - return identifierName != null; - } - - private bool TryGetTypeArgumentList(MemberAccessExpressionSyntax expression, out SyntaxNode typeArgumentList) + private bool IsInterface(SyntaxNode declaration) { - var genericNameEnumerable = expression.ChildNodes().Where(x => x.IsKind(SyntaxKind.GenericName)); - if (genericNameEnumerable.Any()) - { - //TypeArgumentList will always exist inside a GenericName - typeArgumentList = genericNameEnumerable.First().ChildNodes().Where(x => x.IsKind(SyntaxKind.TypeArgumentList)).First(); - return true; - } - - typeArgumentList = null; - return false; + var interfaceKeyword = declaration.ChildTokens().Where(x => x.IsKind(SyntaxKind.InterfaceKeyword)).FirstOrDefault(); + return interfaceKeyword != null && !interfaceKeyword.IsKind(SyntaxKind.None); } } } diff --git a/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/Orchestrator/DateTimeAnalyzer.cs b/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/Orchestrator/DateTimeAnalyzer.cs index 72820341c..97033fcab 100644 --- a/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/Orchestrator/DateTimeAnalyzer.cs +++ b/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/Orchestrator/DateTimeAnalyzer.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. See LICENSE in the project root for license information. using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; @@ -33,16 +32,17 @@ public static bool RegisterDiagnostic(SyntaxNode method, CompilationAnalysisCont if (identifierText == "Now" || identifierText == "UtcNow" || identifierText == "Today") { var memberAccessExpression = identifierName.Parent; - var memberSymbol = SyntaxNodeUtils.GetSyntaxTreeSemanticModel(semanticModel, memberAccessExpression).GetSymbolInfo(memberAccessExpression).Symbol; - - //Covers both DateTime and DateTimeOffset - if (memberSymbol != null && memberSymbol.ToString().StartsWith("System.DateTime")) + if (SyntaxNodeUtils.TryGetISymbol(semanticModel, memberAccessExpression, out ISymbol memberSymbol)) { - var diagnostic = Diagnostic.Create(Rule, memberAccessExpression.GetLocation(), memberAccessExpression); + //Covers both DateTime and DateTimeOffset + if (memberSymbol.ToString().StartsWith("System.DateTime")) + { + var diagnostic = Diagnostic.Create(Rule, memberAccessExpression.GetLocation(), memberAccessExpression); - context.ReportDiagnostic(diagnostic); + context.ReportDiagnostic(diagnostic); - diagnosedIssue = true; + diagnosedIssue = true; + } } } } diff --git a/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/Orchestrator/EnvironmentVariableAnalyzer.cs b/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/Orchestrator/EnvironmentVariableAnalyzer.cs index 039767695..8bd6b8ca5 100644 --- a/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/Orchestrator/EnvironmentVariableAnalyzer.cs +++ b/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/Orchestrator/EnvironmentVariableAnalyzer.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. See LICENSE in the project root for license information. using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; @@ -34,15 +33,16 @@ internal static bool RegisterDiagnostic(SyntaxNode method, CompilationAnalysisCo { var memberAccessExpression = identifierName.Parent; var invocationExpression = memberAccessExpression.Parent; - var memberSymbol = SyntaxNodeUtils.GetSyntaxTreeSemanticModel(semanticModel, memberAccessExpression).GetSymbolInfo(memberAccessExpression).Symbol; - - if (memberSymbol != null && memberSymbol.ToString().StartsWith("System.Environment")) + if (SyntaxNodeUtils.TryGetISymbol(semanticModel, memberAccessExpression, out ISymbol memberSymbol)) { - var diagnostic = Diagnostic.Create(Rule, invocationExpression.GetLocation(), memberAccessExpression); + if (memberSymbol.ToString().StartsWith("System.Environment")) + { + var diagnostic = Diagnostic.Create(Rule, invocationExpression.GetLocation(), memberAccessExpression); - context.ReportDiagnostic(diagnostic); + context.ReportDiagnostic(diagnostic); - diagnosedIssue = true; + diagnosedIssue = true; + } } } } diff --git a/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/Orchestrator/GuidAnalyzer.cs b/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/Orchestrator/GuidAnalyzer.cs index 4e1f5686d..c51e6ad60 100644 --- a/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/Orchestrator/GuidAnalyzer.cs +++ b/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/Orchestrator/GuidAnalyzer.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. See LICENSE in the project root for license information. using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; @@ -33,15 +32,16 @@ internal static bool RegisterDiagnostic(SyntaxNode method, CompilationAnalysisCo { var memberAccessExpression = identifierName.Parent; var invocationExpression = memberAccessExpression.Parent; - var memberSymbol = SyntaxNodeUtils.GetSyntaxTreeSemanticModel(semanticModel, memberAccessExpression).GetSymbolInfo(memberAccessExpression).Symbol; - - if (memberSymbol != null && memberSymbol.ToString().StartsWith("System.Guid")) + if (SyntaxNodeUtils.TryGetISymbol(semanticModel, memberAccessExpression, out ISymbol memberSymbol)) { - var diagnostic = Diagnostic.Create(Rule, invocationExpression.GetLocation(), memberAccessExpression); + if (memberSymbol.ToString().StartsWith("System.Guid")) + { + var diagnostic = Diagnostic.Create(Rule, invocationExpression.GetLocation(), memberAccessExpression); - context.ReportDiagnostic(diagnostic); + context.ReportDiagnostic(diagnostic); - diagnosedIssue = true; + diagnosedIssue = true; + } } } } diff --git a/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/Orchestrator/IOTypesAnalyzer.cs b/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/Orchestrator/IOTypesAnalyzer.cs index ab649bff7..3225653fc 100644 --- a/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/Orchestrator/IOTypesAnalyzer.cs +++ b/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/Orchestrator/IOTypesAnalyzer.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. See LICENSE in the project root for license information. using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; @@ -29,11 +28,9 @@ internal static bool RegisterDiagnostic(SyntaxNode method, CompilationAnalysisCo { if (descendant is IdentifierNameSyntax identifierName) { - var typeInfo = SyntaxNodeUtils.GetSyntaxTreeSemanticModel(semanticModel, identifierName).GetTypeInfo(identifierName); - if (typeInfo.Type != null) + if (SyntaxNodeUtils.TryGetITypeSymbol(semanticModel, identifierName, out ITypeSymbol type)) { - var type = typeInfo.Type.ToString(); - if (IsIOClass(type)) + if (IsIOClass(type.ToString())) { var diagnostic = Diagnostic.Create(Rule, identifierName.Identifier.GetLocation(), type); diff --git a/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/Orchestrator/MethodAnalyzer.cs b/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/Orchestrator/MethodAnalyzer.cs index d9879c694..d8938f5b5 100644 --- a/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/Orchestrator/MethodAnalyzer.cs +++ b/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/Orchestrator/MethodAnalyzer.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. See LICENSE in the project root for license information. using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using System.Collections.Generic; @@ -81,7 +80,7 @@ private static List AnalyzeMethod(SyntaxNode methodDeclaratio { if (descendant is InvocationExpressionSyntax invocation) { - if (SyntaxNodeUtils.GetSyntaxTreeSemanticModel(semanticModel, invocation).GetSymbolInfo(invocation).Symbol is IMethodSymbol methodSymbol) + if (SyntaxNodeUtils.TryGetISymbol(semanticModel, invocation, out ISymbol symbol) && symbol is IMethodSymbol methodSymbol) { var syntaxReference = methodSymbol.DeclaringSyntaxReferences.FirstOrDefault(); if (syntaxReference != null) diff --git a/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/Orchestrator/ThreadTaskAnalyzer.cs b/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/Orchestrator/ThreadTaskAnalyzer.cs index 546f3b570..d3243d458 100644 --- a/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/Orchestrator/ThreadTaskAnalyzer.cs +++ b/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/Orchestrator/ThreadTaskAnalyzer.cs @@ -43,15 +43,16 @@ private static bool AnalyzeIdentifierTask(SyntaxNode method, CompilationAnalysis if (identifierText == "Run" || identifierText == "Factory.StartNew") { var memberAccessExpression = identifierName.Parent; - var memberSymbol = SyntaxNodeUtils.GetSyntaxTreeSemanticModel(semanticModel, memberAccessExpression).GetSymbolInfo(memberAccessExpression).Symbol; - - if (memberSymbol != null && memberSymbol.ToString().StartsWith("System.Threading.Tasks.Task")) + if (SyntaxNodeUtils.TryGetISymbol(semanticModel, memberAccessExpression, out ISymbol memberSymbol)) { - var diagnostic = Diagnostic.Create(Rule, memberAccessExpression.GetLocation(), memberAccessExpression); + if (memberSymbol.ToString().StartsWith("System.Threading.Tasks.Task")) + { + var diagnostic = Diagnostic.Create(Rule, memberAccessExpression.GetLocation(), memberAccessExpression); - context.ReportDiagnostic(diagnostic); + context.ReportDiagnostic(diagnostic); - diagnosedIssue = true; + diagnosedIssue = true; + } } } } @@ -72,15 +73,16 @@ private static bool AnalyzeIdentifierTaskFactory(SyntaxNode method, CompilationA if (identifierText == "StartNew") { var memberAccessExpression = identifierName.Parent; - var memberSymbol = SyntaxNodeUtils.GetSyntaxTreeSemanticModel(semanticModel, memberAccessExpression).GetSymbolInfo(memberAccessExpression).Symbol; - - if (memberSymbol != null && memberSymbol.ToString().StartsWith("System.Threading.Tasks.TaskFactory")) + if (SyntaxNodeUtils.TryGetISymbol(semanticModel, memberAccessExpression, out ISymbol memberSymbol)) { - var diagnostic = Diagnostic.Create(Rule, memberAccessExpression.GetLocation(), memberAccessExpression); + if (memberSymbol.ToString().StartsWith("System.Threading.Tasks.TaskFactory")) + { + var diagnostic = Diagnostic.Create(Rule, memberAccessExpression.GetLocation(), memberAccessExpression); - context.ReportDiagnostic(diagnostic); + context.ReportDiagnostic(diagnostic); - diagnosedIssue = true; + diagnosedIssue = true; + } } } } @@ -101,15 +103,16 @@ private static bool AnalyzeIdentifierThread(SyntaxNode method, CompilationAnalys if (identifierText == "Start") { var memberAccessExpression = identifierName.Parent; - var memberSymbol = SyntaxNodeUtils.GetSyntaxTreeSemanticModel(semanticModel, memberAccessExpression).GetSymbolInfo(memberAccessExpression).Symbol; - - if (memberSymbol != null && memberSymbol.ToString().StartsWith("System.Threading.Thread")) + if (SyntaxNodeUtils.TryGetISymbol(semanticModel, memberAccessExpression, out ISymbol memberSymbol)) { - var diagnostic = Diagnostic.Create(Rule, memberAccessExpression.GetLocation(), memberAccessExpression); + if (memberSymbol != null && memberSymbol.ToString().StartsWith("System.Threading.Thread")) + { + var diagnostic = Diagnostic.Create(Rule, memberAccessExpression.GetLocation(), memberAccessExpression); - context.ReportDiagnostic(diagnostic); + context.ReportDiagnostic(diagnostic); - diagnosedIssue = true; + diagnosedIssue = true; + } } } } @@ -130,17 +133,18 @@ private static bool AnalyzeIdentifierTaskContinueWith(SyntaxNode method, Compila if (identifierText == "ContinueWith") { var memberAccessExpression = identifierName.Parent; - var memberSymbol = SyntaxNodeUtils.GetSyntaxTreeSemanticModel(semanticModel, memberAccessExpression).GetSymbolInfo(memberAccessExpression).Symbol; - - if (memberSymbol != null && memberSymbol.ToString().StartsWith("System.Threading.Tasks.Task")) + if (SyntaxNodeUtils.TryGetISymbol(semanticModel, memberAccessExpression, out ISymbol memberSymbol)) { - if (!HasExecuteSynchronously(identifierName)) + if (memberSymbol != null && memberSymbol.ToString().StartsWith("System.Threading.Tasks.Task")) { - var diagnostic = Diagnostic.Create(Rule, memberAccessExpression.GetLocation(), memberAccessExpression); + if (!HasExecuteSynchronously(identifierName)) + { + var diagnostic = Diagnostic.Create(Rule, memberAccessExpression.GetLocation(), memberAccessExpression); - context.ReportDiagnostic(diagnostic); + context.ReportDiagnostic(diagnostic); - diagnosedIssue = true; + diagnosedIssue = true; + } } } } diff --git a/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/Orchestrator/TimerAnalyzer.cs b/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/Orchestrator/TimerAnalyzer.cs index ef9558d6b..959c01fa8 100644 --- a/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/Orchestrator/TimerAnalyzer.cs +++ b/src/WebJobs.Extensions.DurableTask.Analyzers/Analyzers/Orchestrator/TimerAnalyzer.cs @@ -46,19 +46,20 @@ private static bool AnalyzeIdentifierTask(SyntaxNode method, CompilationAnalysis if (identifierText == "Delay") { var memberAccessExpression = identifierName.Parent; - var memberSymbol = SyntaxNodeUtils.GetSyntaxTreeSemanticModel(semanticModel, memberAccessExpression).GetSymbolInfo(memberAccessExpression).Symbol; - - if (memberSymbol != null && memberSymbol.ToString().StartsWith("System.Threading.Tasks.Task")) + if (SyntaxNodeUtils.TryGetISymbol(semanticModel, memberAccessExpression, out ISymbol memberSymbol)) { - if (TryGetRuleFromVersion(out DiagnosticDescriptor rule)) + if (memberSymbol != null && memberSymbol.ToString().StartsWith("System.Threading.Tasks.Task")) { - var expression = GetAwaitOrInvocationExpression(memberAccessExpression); + if (TryGetRuleFromVersion(out DiagnosticDescriptor rule)) + { + var expression = GetAwaitOrInvocationExpression(memberAccessExpression); - var diagnostic = Diagnostic.Create(rule, expression.GetLocation(), expression); + var diagnostic = Diagnostic.Create(rule, expression.GetLocation(), expression); - context.ReportDiagnostic(diagnostic); + context.ReportDiagnostic(diagnostic); - diagnosedIssue = true; + diagnosedIssue = true; + } } } } @@ -94,19 +95,20 @@ private static bool AnalyzeIdentifierThread(SyntaxNode method, CompilationAnalys if (identifierText == "Sleep") { var memberAccessExpression = identifierName.Parent; - var memberSymbol = SyntaxNodeUtils.GetSyntaxTreeSemanticModel(semanticModel, memberAccessExpression).GetSymbolInfo(memberAccessExpression).Symbol; - - if (memberSymbol != null && memberSymbol.ToString().StartsWith("System.Threading.Thread")) + if (SyntaxNodeUtils.TryGetISymbol(semanticModel, memberAccessExpression, out ISymbol memberSymbol)) { - if (TryGetRuleFromVersion(out DiagnosticDescriptor rule)) + if (memberSymbol != null && memberSymbol.ToString().StartsWith("System.Threading.Thread")) { - var expression = GetAwaitOrInvocationExpression(memberAccessExpression); + if (TryGetRuleFromVersion(out DiagnosticDescriptor rule)) + { + var expression = GetAwaitOrInvocationExpression(memberAccessExpression); - var diagnostic = Diagnostic.Create(rule, expression.GetLocation(), expression); + var diagnostic = Diagnostic.Create(rule, expression.GetLocation(), expression); - context.ReportDiagnostic(diagnostic); + context.ReportDiagnostic(diagnostic); - diagnosedIssue = true; + diagnosedIssue = true; + } } } } diff --git a/src/WebJobs.Extensions.DurableTask.Analyzers/CodeFixProviderUtils.cs b/src/WebJobs.Extensions.DurableTask.Analyzers/CodeFixProviderUtils.cs index 80a3dded9..907606b09 100644 --- a/src/WebJobs.Extensions.DurableTask.Analyzers/CodeFixProviderUtils.cs +++ b/src/WebJobs.Extensions.DurableTask.Analyzers/CodeFixProviderUtils.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. See LICENSE in the project root for license information. using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp; using System.Linq; using System.Threading; @@ -31,10 +30,10 @@ public static bool TryGetDurableOrchestrationContextVariableName(SyntaxNode node foreach (SyntaxNode parameter in parameterList.ChildNodes()) { - var attributeListEnumerable = parameter.ChildNodes().Where(x => x.IsKind(SyntaxKind.AttributeList)); - foreach (SyntaxNode attribute in attributeListEnumerable) + var attributeLists = parameter.ChildNodes().Where(x => x.IsKind(SyntaxKind.AttributeList)); + foreach (SyntaxNode attributeList in attributeLists) { - if (attribute.ChildNodes().First().ToString().Equals("OrchestrationTrigger")) + if (attributeList.ChildNodes().First().ToString().Equals("OrchestrationTrigger")) { var identifierName = parameter.ChildNodes().Where(x => x.IsKind(SyntaxKind.IdentifierName)).FirstOrDefault()?.ToString(); if (string.Equals(identifierName, "IDurableOrchestrationContext") || string.Equals(identifierName, "DurableOrchestrationContext") || string.Equals(identifierName, "DurableOrchestrationContextBase")) diff --git a/src/WebJobs.Extensions.DurableTask.Analyzers/CodefixProviders/Orchestrator/TimerCodeFixProvider.cs b/src/WebJobs.Extensions.DurableTask.Analyzers/CodefixProviders/Orchestrator/TimerCodeFixProvider.cs index ac58f0976..1f0439c9c 100644 --- a/src/WebJobs.Extensions.DurableTask.Analyzers/CodefixProviders/Orchestrator/TimerCodeFixProvider.cs +++ b/src/WebJobs.Extensions.DurableTask.Analyzers/CodefixProviders/Orchestrator/TimerCodeFixProvider.cs @@ -155,8 +155,8 @@ private bool TryGetParameterOfType(SyntaxNode argument, SemanticModel semanticMo foreach (SyntaxNode identifier in identifiers) { if (string.Equals(identifier.ToString(), typeWithoutNamespace) || - TryGetTypeName(semanticModel, identifier, out string typeName) && - string.Equals(typeName, typeToCompare)) + SyntaxNodeUtils.TryGetITypeSymbol(semanticModel, identifier, out ITypeSymbol type) && + string.Equals(type.ToString(), typeToCompare)) { parameter = node.First().ToString(); return true; @@ -201,19 +201,6 @@ private static bool TryGetChildNodes(SyntaxNode argument, SyntaxKind kind, out I return false; } - - private static bool TryGetTypeName(SemanticModel semanticModel, SyntaxNode identifier, out string typeName) - { - var typeInfo = semanticModel.GetTypeInfo(identifier); - if (typeInfo.Type != null) - { - typeName = typeInfo.Type.ToString(); - return true; - } - - typeName = null; - return false; - } private static string GetTypeWithoutNamespace(string type) { diff --git a/src/WebJobs.Extensions.DurableTask.Analyzers/EntityInterface.cs b/src/WebJobs.Extensions.DurableTask.Analyzers/EntityInterface.cs index 1874703a6..d1d97ed4a 100644 --- a/src/WebJobs.Extensions.DurableTask.Analyzers/EntityInterface.cs +++ b/src/WebJobs.Extensions.DurableTask.Analyzers/EntityInterface.cs @@ -2,16 +2,13 @@ // Licensed under the MIT License. See LICENSE in the project root for license information. using Microsoft.CodeAnalysis; -using System; namespace Microsoft.Azure.WebJobs.Extensions.DurableTask.Analyzers { public class EntityInterface { - public String name { get; set; } + public string Name { get; set; } public SyntaxNode InterfaceDeclaration { get; set; } - - public ITypeSymbol typeSymbol { get; set; } } } diff --git a/src/WebJobs.Extensions.DurableTask.Analyzers/Resources.Designer.cs b/src/WebJobs.Extensions.DurableTask.Analyzers/Resources.Designer.cs index 86d34e709..0ed1be765 100644 --- a/src/WebJobs.Extensions.DurableTask.Analyzers/Resources.Designer.cs +++ b/src/WebJobs.Extensions.DurableTask.Analyzers/Resources.Designer.cs @@ -19,7 +19,7 @@ namespace Microsoft.Azure.WebJobs.Extensions.DurableTask.Analyzers { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] public class Resources { @@ -159,6 +159,24 @@ public static string ActivityReturnTypeAnalyzerTitle { } } + /// + /// Looks up a localized string similar to Code used in an orchestrator must not use await on non-Durable Functions methods.. + /// + public static string AwaitAnalyzerMessageFormat { + get { + return ResourceManager.GetString("AwaitAnalyzerMessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Code used in an orchestrator must not use await on non-Durable Functions methods.. + /// + public static string AwaitAnalyzerTitle { + get { + return ResourceManager.GetString("AwaitAnalyzerTitle", resourceCulture); + } + } + /// /// Looks up a localized string similar to DateTime calls must be deterministic inside an orchestrator function.. /// diff --git a/src/WebJobs.Extensions.DurableTask.Analyzers/Resources.resx b/src/WebJobs.Extensions.DurableTask.Analyzers/Resources.resx index e68f80362..86844f286 100644 --- a/src/WebJobs.Extensions.DurableTask.Analyzers/Resources.resx +++ b/src/WebJobs.Extensions.DurableTask.Analyzers/Resources.resx @@ -150,6 +150,12 @@ Activity function call return type doesn't match function definition return type. + + Code used in an orchestrator must not use await on non-Durable Functions methods. + + + Code used in an orchestrator must not use await on non-Durable Functions methods. + DateTime calls must be deterministic inside an orchestrator function. diff --git a/src/WebJobs.Extensions.DurableTask.Analyzers/SyntaxNodeUtils.cs b/src/WebJobs.Extensions.DurableTask.Analyzers/SyntaxNodeUtils.cs index c4c83f440..07a0123c2 100644 --- a/src/WebJobs.Extensions.DurableTask.Analyzers/SyntaxNodeUtils.cs +++ b/src/WebJobs.Extensions.DurableTask.Analyzers/SyntaxNodeUtils.cs @@ -5,8 +5,8 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; -using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; namespace Microsoft.Azure.WebJobs.Extensions.DurableTask.Analyzers @@ -29,11 +29,48 @@ public static DurableVersion GetDurableVersion(SemanticModel semanticModel) public static SemanticModel GetSyntaxTreeSemanticModel(SemanticModel model, SyntaxNode node) { + if (model == null || node == null) + { + return null; + } + return model.SyntaxTree == node.SyntaxTree ? model : model.Compilation.GetSemanticModel(node.SyntaxTree); } + public static bool TryGetITypeSymbol(SemanticModel semanticModel, SyntaxNode node, out ITypeSymbol typeSymbol) + { + if (node != null) + { + semanticModel = GetSyntaxTreeSemanticModel(semanticModel, node); + if (semanticModel != null) + { + typeSymbol = semanticModel.GetTypeInfo(node).Type; + return typeSymbol != null; + } + } + + typeSymbol = null; + return false; + } + + public static bool TryGetISymbol(SemanticModel semanticModel, SyntaxNode node, out ISymbol symbol) + { + if (node != null) + { + semanticModel = GetSyntaxTreeSemanticModel(semanticModel, node); + if (semanticModel != null) + { + symbol = semanticModel.GetSymbolInfo(node).Symbol; + return symbol != null; + } + } + + symbol = null; + return false; + } + public static bool TryGetClosestString(string name, IEnumerable availableNames, out string closestString) { closestString = availableNames.OrderBy(x => x.LevenshteinDistance(name)).FirstOrDefault(); @@ -89,7 +126,8 @@ private static bool TryGetAttribute(SyntaxNode node, string attributeName, out S { //An AttributeList will always have a child node Attribute attribute = attributeList.ChildNodes().First(); - if (attribute.ChildNodes().First().ToString().Equals(attributeName)) + + if (attribute.ChildNodes().Any() && attribute.ChildNodes().First().ToString().Equals(attributeName)) { return true; } @@ -181,16 +219,19 @@ public static bool TryParseFunctionName(SemanticModel semanticModel, SyntaxNode } var newSemanticModel = GetSyntaxTreeSemanticModel(semanticModel, node); - if (TryGetFunctionNameInConstant(newSemanticModel, node, out functionName)) + if (newSemanticModel != null) { - return true; + if (TryGetFunctionNameInConstant(newSemanticModel, node, out functionName)) + { + return true; + } } functionName = null; return false; } - private static bool TryGetFunctionNameInConstant(SemanticModel semanticModel, SyntaxNode node, out string functionName) + public static bool TryGetFunctionNameInConstant(SemanticModel semanticModel, SyntaxNode node, out string functionName) { if (node != null && (node.IsKind(SyntaxKind.IdentifierName) || node.IsKind(SyntaxKind.SimpleMemberAccessExpression))) { @@ -277,23 +318,25 @@ internal static bool TryGetParameterNodeNextToAttribute(SyntaxNodeAnalysisContex return TryGetChildTypeNode(parameter, out inputType); } - internal static bool TryGetTypeArgumentNode(MemberAccessExpressionSyntax expression, out SyntaxNode identifierNode) + internal static bool TryGetTypeArgumentIdentifier(MemberAccessExpressionSyntax expression, out SyntaxNode identifierNode) { var genericName = expression.ChildNodes().Where(x => x.IsKind(SyntaxKind.GenericName)).FirstOrDefault(); if (genericName != null) { - //GenericName will always have a TypeArgumentList - var typeArgumentList = genericName.ChildNodes().Where(x => x.IsKind(SyntaxKind.TypeArgumentList)).First(); - - //TypeArgumentList will always have a child node - identifierNode = typeArgumentList.ChildNodes().First(); - return true; + return TryGetTypeArgumentIdentifier((GenericNameSyntax)genericName, out identifierNode); } identifierNode = null; return false; } + internal static bool TryGetTypeArgumentIdentifier(GenericNameSyntax node, out SyntaxNode identifierNode) + { + //GenericName will always have a TypeArgumentList + identifierNode = node.TypeArgumentList.ChildNodes().First(); + return (identifierNode != null && !identifierNode.IsKind(SyntaxKind.OmittedTypeArgument)); + } + internal static bool IsActivityTriggerAttribute(AttributeSyntax attribute) => IsSpecifiedAttribute(attribute, "ActivityTrigger"); internal static bool IsEntityTriggerAttribute(AttributeSyntax attribute) => IsSpecifiedAttribute(attribute, "EntityTrigger"); @@ -308,133 +351,208 @@ private static bool IsSpecifiedAttribute(AttributeSyntax attribute, string attri return false; } - internal static string GetQualifiedTypeName(ITypeSymbol typeInfo) + public static bool IsDurableActivityContext(ITypeSymbol type) { - if (typeInfo != null) + if (type == null) { - if (typeInfo is INamedTypeSymbol namedTypeInfo) - { - var tupleUnderlyingType = namedTypeInfo.TupleUnderlyingType; - if (tupleUnderlyingType != null) - { - return $"({string.Join(", ", tupleUnderlyingType.TypeArguments.Select(x => x.ToString()))})"; - } + return false; + } - return typeInfo.ToString(); - } + return (type.ToString().Equals("Microsoft.Azure.WebJobs.Extensions.DurableTask.IDurableActivityContext") + || type.ToString().Equals("Microsoft.Azure.WebJobs.DurableActivityContext") + || type.ToString().Equals("Microsoft.Azure.WebJobs.DurableActivityContextBase")); + } - var arrayString = ""; - if (typeInfo.Kind.Equals(SymbolKind.ArrayType)) - { - arrayString = "[]"; - typeInfo = ((IArrayTypeSymbol)typeInfo).ElementType; - } + public static bool IsMatchingDerivedOrCompatibleType(ITypeSymbol subclassOrMatching, ITypeSymbol superOrMatching) + { + if (subclassOrMatching == null || superOrMatching == null) + { + return false; + } + + return (subclassOrMatching.Equals(superOrMatching) + || AreMatchingValueTuples(subclassOrMatching, superOrMatching) + || AreMatchingGenericTypes(subclassOrMatching, superOrMatching) + || IsSubclassOrImplementation(subclassOrMatching, superOrMatching) + || AreCompatibleIEnumerableTypes(subclassOrMatching, superOrMatching)); + } - if (!string.IsNullOrEmpty(typeInfo.Name)) + private static bool AreMatchingValueTuples(ITypeSymbol subclassOrMatching, ITypeSymbol superOrMatching) + { + if (subclassOrMatching == null || superOrMatching == null) + { + return false; + } + + if (subclassOrMatching.IsTupleType && superOrMatching.IsTupleType) + { + return HaveMatchingOrCompatibeTypeArguments(subclassOrMatching, superOrMatching); + } + + return false; + } + + private static bool HaveMatchingOrCompatibeTypeArguments(ITypeSymbol subclassOrMatching, ITypeSymbol superOrMatching) + { + if (subclassOrMatching == null || superOrMatching == null + || !(subclassOrMatching is INamedTypeSymbol subclassNamedType + && superOrMatching is INamedTypeSymbol superNamedType)) + { + return false; + } + + var subclassTypeArguments = subclassNamedType.TypeArguments; + var superTypeArguments = superNamedType.TypeArguments; + + if (NotNullAndMatchingLength(subclassTypeArguments, superTypeArguments)) + { + for (int i = 0; i < subclassTypeArguments.Length; i++) { - return typeInfo.ContainingNamespace?.ToString() + "." + typeInfo.Name.ToString() + arrayString; + if (!IsMatchingDerivedOrCompatibleType(subclassTypeArguments[i], superTypeArguments[i])) + { + return false; + } } + + return true; } - return "Unknown Type"; + return false; } - internal static bool InputMatchesOrCompatibleType(ITypeSymbol invocationType, ITypeSymbol definitionType) + private static bool NotNullAndMatchingLength(ImmutableArray immutableArrayOne, ImmutableArray immutableArrayTwo) { - if (invocationType == null || definitionType == null) + if (immutableArrayOne != null && immutableArrayOne != null) { - return false; + return immutableArrayOne.Length == immutableArrayTwo.Length; } - return invocationType.Equals(definitionType) - || AreCompatibleIEnumerableTypes(invocationType, definitionType) - || AreEqualQualifiedTypeNames(invocationType, definitionType); + return false; } - private static bool AreEqualQualifiedTypeNames(ITypeSymbol invocationType, ITypeSymbol definitionType) + private static bool AreMatchingGenericTypes(ITypeSymbol subclassOrMatching, ITypeSymbol superOrMatching) { - var invocationQualifiedName = GetQualifiedTypeName(invocationType); - var definitionQualifiedName = GetQualifiedTypeName(definitionType); + if (subclassOrMatching == null || superOrMatching == null) + { + return false; + } + + if (subclassOrMatching.Name == superOrMatching.Name) + { + return HaveMatchingOrCompatibeTypeArguments(subclassOrMatching, superOrMatching); + } - return invocationQualifiedName.Equals(definitionQualifiedName); + return false; } - private static bool AreCompatibleIEnumerableTypes(ITypeSymbol invocationType, ITypeSymbol functionType) + private static bool IsSubclassOrImplementation(ITypeSymbol subclassOrImplementation, ITypeSymbol superOrInterface) { - if (AreArrayOrNamedTypes(invocationType, functionType) && UnderlyingTypesMatch(invocationType, functionType)) + if (subclassOrImplementation == null || superOrInterface == null) + { + return false; + } + + var superOrInterfaceName = superOrInterface is IArrayTypeSymbol arrayType ? arrayType.ElementType.Name : superOrInterface.Name; + + if (TypeSymbolImplementsOrExtendsType(subclassOrImplementation, superOrInterfaceName)) { - return TypeNodeImplementsOrExtendsType(invocationType, "IEnumerable") - && TypeNodeImplementsOrExtendsType(functionType, "IEnumerable"); + return HaveMatchingOrCompatibeTypeArguments(subclassOrImplementation, superOrInterface); } return false; } - public static bool TypeNodeImplementsOrExtendsType(ITypeSymbol node, string interfaceOrBase) + private static bool AreCompatibleIEnumerableTypes(ITypeSymbol typeOne, ITypeSymbol typeTwo) { - if (string.IsNullOrEmpty(interfaceOrBase)) + if (typeOne == null || typeTwo == null) { return false; } - return node.AllInterfaces.Any(i => i.Name.Equals(interfaceOrBase)) - || TypeNodeIsSubclass(node, interfaceOrBase); + if (CollectionTypesMatch(typeOne, typeTwo)) + { + return TypeSymbolImplementsOrExtendsType(typeOne, "IEnumerable") + && TypeSymbolImplementsOrExtendsType(typeTwo, "IEnumerable"); + } + return false; } - private static bool TypeNodeIsSubclass(ITypeSymbol node, string baseClass) + private static bool CollectionTypesMatch(ITypeSymbol typeOne, ITypeSymbol typeTwo) { - if (node == null || string.IsNullOrEmpty(baseClass)) + if (typeOne == null || typeTwo == null) { return false; } - var curr = node.BaseType; - while (curr != null) + return (TryGetCollectionType(typeOne, out ITypeSymbol invocationCollectionType) + && TryGetCollectionType(typeTwo, out ITypeSymbol functionCollectionType) + && IsMatchingDerivedOrCompatibleType(invocationCollectionType, functionCollectionType)); + } + + private static bool TryGetCollectionType(ITypeSymbol type, out ITypeSymbol collectionType) + { + if (type != null) { - if (curr.ToString().Equals(baseClass)) + if (type.Kind.Equals(SymbolKind.ArrayType)) { + collectionType = ((IArrayTypeSymbol)type).ElementType; return true; } - curr = curr.BaseType; + if (type.Kind.Equals(SymbolKind.NamedType)) + { + collectionType = ((INamedTypeSymbol)type).TypeArguments.FirstOrDefault(); + return collectionType != null; + } } + collectionType = null; return false; - } - private static bool AreArrayOrNamedTypes(ITypeSymbol invocationType, ITypeSymbol functionType) + public static bool TypeSymbolImplementsOrExtendsType(ITypeSymbol node, string interfaceOrBase) { - return (invocationType.Kind.Equals(SymbolKind.ArrayType) || invocationType.Kind.Equals(SymbolKind.NamedType)) - && (functionType.Kind.Equals(SymbolKind.ArrayType) || functionType.Kind.Equals(SymbolKind.NamedType)); - } + if (node == null || string.IsNullOrEmpty(interfaceOrBase)) + { + return false; + } + + return TypeSymbolImplementsInterface(node, interfaceOrBase) + || TypeSymbolIsSubclass(node, interfaceOrBase); - private static bool UnderlyingTypesMatch(ITypeSymbol invocationType, ITypeSymbol functionType) - { - return (TryGetUnderlyingType(invocationType, out ITypeSymbol invocationUnderlyingType) - && TryGetUnderlyingType(functionType, out ITypeSymbol functionUnderlyingType) - && invocationUnderlyingType.Name.Equals(functionUnderlyingType.Name)); } - private static bool TryGetUnderlyingType(ITypeSymbol type, out ITypeSymbol underlyingType) + private static bool TypeSymbolImplementsInterface(ITypeSymbol node, string interfaceName) { - if (type.Kind.Equals(SymbolKind.ArrayType)) + if (node == null || string.IsNullOrEmpty(interfaceName)) { - underlyingType = ((IArrayTypeSymbol)type).ElementType; - return true; + return false; } - if (type.Kind.Equals(SymbolKind.NamedType)) + return node.AllInterfaces.Any(i => i.Name.Equals(interfaceName)); + } + + private static bool TypeSymbolIsSubclass(ITypeSymbol node, string baseClass) + { + if (node == null || string.IsNullOrEmpty(baseClass)) { - underlyingType = ((INamedTypeSymbol)type).TypeArguments.FirstOrDefault(); - return underlyingType != null; + return false; } - else + + var curr = node.BaseType; + while (curr != null) { - underlyingType = null; - return false; + if (curr.Name.Equals(baseClass)) + { + return true; + } + + curr = curr.BaseType; } + + return false; + } } } diff --git a/src/WebJobs.Extensions.DurableTask.Analyzers/WebJobs.Extensions.DurableTask.Analyzers.csproj b/src/WebJobs.Extensions.DurableTask.Analyzers/WebJobs.Extensions.DurableTask.Analyzers.csproj index 3bf34b1d8..607ff9fea 100644 --- a/src/WebJobs.Extensions.DurableTask.Analyzers/WebJobs.Extensions.DurableTask.Analyzers.csproj +++ b/src/WebJobs.Extensions.DurableTask.Analyzers/WebJobs.Extensions.DurableTask.Analyzers.csproj @@ -8,7 +8,7 @@ Microsoft.Azure.WebJobs.Extensions.DurableTask.Analyzers - 0.2.3 + 0.3.0 Microsoft https://go.microsoft.com/fwlink/?linkid=2028464 https://github.com/Azure/azure-functions-durable-extension diff --git a/src/WebJobs.Extensions.DurableTask/AzureStorageDurabilityProvider.cs b/src/WebJobs.Extensions.DurableTask/AzureStorageDurabilityProvider.cs index 429ccd3bd..24bbde52c 100644 --- a/src/WebJobs.Extensions.DurableTask/AzureStorageDurabilityProvider.cs +++ b/src/WebJobs.Extensions.DurableTask/AzureStorageDurabilityProvider.cs @@ -22,8 +22,6 @@ namespace Microsoft.Azure.WebJobs.Extensions.DurableTask /// internal class AzureStorageDurabilityProvider : DurabilityProvider { - private static readonly TimeSpan MaxTimerDuration = TimeSpan.FromDays(6); - private readonly AzureStorageOrchestrationService serviceClient; private readonly string connectionName; private readonly JObject storageOptionsJson; @@ -54,6 +52,10 @@ public AzureStorageDurabilityProvider( public override JObject ConfigurationJson => this.storageOptionsJson; + public override TimeSpan MaximumDelayTime { get; set; } = TimeSpan.FromDays(6); + + public override TimeSpan LongRunningTimerIntervalLength { get; set; } = TimeSpan.FromDays(3); + /// public async override Task> GetAllOrchestrationStates(CancellationToken cancellationToken) { @@ -137,9 +139,9 @@ public async override Task GetOrchestrationState public override bool ValidateDelayTime(TimeSpan timespan, out string errorMessage) { - if (timespan > MaxTimerDuration) + if (timespan > this.MaximumDelayTime) { - errorMessage = $"The Azure Storage provider supports a maximum of {MaxTimerDuration.TotalDays} days for time-based delays"; + errorMessage = $"The Azure Storage provider supports a maximum of {this.MaximumDelayTime.TotalDays} days for time-based delays"; return false; } diff --git a/src/WebJobs.Extensions.DurableTask/AzureStorageDurabilityProviderFactory.cs b/src/WebJobs.Extensions.DurableTask/AzureStorageDurabilityProviderFactory.cs index 639b3377a..e4cd3f377 100644 --- a/src/WebJobs.Extensions.DurableTask/AzureStorageDurabilityProviderFactory.cs +++ b/src/WebJobs.Extensions.DurableTask/AzureStorageDurabilityProviderFactory.cs @@ -1,185 +1,194 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. See LICENSE in the project root for license information. - -using System; -using DurableTask.AzureStorage; -using Microsoft.Extensions.Options; -using Newtonsoft.Json; - -namespace Microsoft.Azure.WebJobs.Extensions.DurableTask -{ - internal class AzureStorageDurabilityProviderFactory : IDurabilityProviderFactory - { - private readonly DurableTaskOptions options; - private readonly AzureStorageOptions azureStorageOptions; - private readonly IConnectionStringResolver connectionStringResolver; - private readonly string defaultConnectionName; - private readonly INameResolver nameResolver; - private AzureStorageDurabilityProvider defaultStorageProvider; - - // Must wait to get settings until we have validated taskhub name. - private bool hasValidatedOptions; - private AzureStorageOrchestrationServiceSettings defaultSettings; - - public AzureStorageDurabilityProviderFactory( - IOptions options, - IConnectionStringResolver connectionStringResolver, - INameResolver nameResolver) - { - this.options = options.Value; - this.nameResolver = nameResolver; - this.azureStorageOptions = new AzureStorageOptions(); - JsonConvert.PopulateObject(JsonConvert.SerializeObject(this.options.StorageProvider), this.azureStorageOptions); - - this.azureStorageOptions.Validate(); - - this.connectionStringResolver = connectionStringResolver ?? throw new ArgumentNullException(nameof(connectionStringResolver)); - this.defaultConnectionName = this.azureStorageOptions.ConnectionStringName ?? ConnectionStringNames.Storage; - } - - internal string GetDefaultStorageConnectionString() - { - return this.connectionStringResolver.Resolve(this.defaultConnectionName); - } - - // This method should not be called before the app settings are resolved into the options. - // Because of this, we wait to validate the options until right before building a durability provider, rather - // than in the Factory constructor. - private void EnsureInitialized() - { - if (!this.hasValidatedOptions) - { - if (!this.options.IsDefaultHubName()) - { - this.azureStorageOptions.ValidateHubName(this.options.HubName); - } - else if (!this.azureStorageOptions.IsSanitizedHubName(this.options.HubName, out string sanitizedHubName)) - { - this.options.SetDefaultHubName(sanitizedHubName); - } - - this.defaultSettings = this.GetAzureStorageOrchestrationServiceSettings(); - this.hasValidatedOptions = true; - } - } - - public DurabilityProvider GetDurabilityProvider() - { - this.EnsureInitialized(); - if (this.defaultStorageProvider == null) - { - var defaultService = new AzureStorageOrchestrationService(this.defaultSettings); - this.defaultStorageProvider = new AzureStorageDurabilityProvider( - defaultService, - this.defaultConnectionName, - this.azureStorageOptions); - } - - return this.defaultStorageProvider; - } - - public DurabilityProvider GetDurabilityProvider(DurableClientAttribute attribute) - { - this.EnsureInitialized(); - return this.GetAzureStorageStorageProvider(attribute); - } - - private AzureStorageDurabilityProvider GetAzureStorageStorageProvider(DurableClientAttribute attribute) - { - string connectionName = attribute.ConnectionName ?? this.defaultConnectionName; - AzureStorageOrchestrationServiceSettings settings = this.GetAzureStorageOrchestrationServiceSettings(connectionName, attribute.TaskHub); - - AzureStorageDurabilityProvider innerClient; - if (string.Equals(this.defaultSettings.TaskHubName, settings.TaskHubName, StringComparison.OrdinalIgnoreCase) && - string.Equals(this.defaultSettings.StorageConnectionString, settings.StorageConnectionString, StringComparison.OrdinalIgnoreCase)) - { - // It's important that clients use the same AzureStorageOrchestrationService instance - // as the host when possible to ensure we any send operations can be picked up - // immediately instead of waiting for the next queue polling interval. - innerClient = this.defaultStorageProvider; - } - else - { - innerClient = new AzureStorageDurabilityProvider( - new AzureStorageOrchestrationService(settings), - connectionName, - this.azureStorageOptions); - } - - return innerClient; - } - - internal AzureStorageOrchestrationServiceSettings GetAzureStorageOrchestrationServiceSettings( - string connectionName = null, - string taskHubNameOverride = null) - { - connectionName = connectionName ?? this.defaultConnectionName; - - string resolvedStorageConnectionString = this.connectionStringResolver.Resolve(connectionName); - - if (string.IsNullOrEmpty(resolvedStorageConnectionString)) - { - throw new InvalidOperationException("Unable to find an Azure Storage connection string to use for this binding."); - } - - TimeSpan extendedSessionTimeout = TimeSpan.FromSeconds( - Math.Max(this.options.ExtendedSessionIdleTimeoutInSeconds, 0)); - - var settings = new AzureStorageOrchestrationServiceSettings - { - StorageConnectionString = resolvedStorageConnectionString, - TaskHubName = taskHubNameOverride ?? this.options.HubName, - PartitionCount = this.azureStorageOptions.PartitionCount, - ControlQueueBatchSize = this.azureStorageOptions.ControlQueueBatchSize, - ControlQueueBufferThreshold = this.azureStorageOptions.ControlQueueBufferThreshold, - ControlQueueVisibilityTimeout = this.azureStorageOptions.ControlQueueVisibilityTimeout, - WorkItemQueueVisibilityTimeout = this.azureStorageOptions.WorkItemQueueVisibilityTimeout, - MaxConcurrentTaskOrchestrationWorkItems = this.options.MaxConcurrentOrchestratorFunctions, - MaxConcurrentTaskActivityWorkItems = this.options.MaxConcurrentActivityFunctions, - ExtendedSessionsEnabled = this.options.ExtendedSessionsEnabled, - ExtendedSessionIdleTimeout = extendedSessionTimeout, - MaxQueuePollingInterval = this.azureStorageOptions.MaxQueuePollingInterval, - TrackingStoreStorageAccountDetails = GetStorageAccountDetailsOrNull( - this.connectionStringResolver, - this.azureStorageOptions.TrackingStoreConnectionStringName), - FetchLargeMessageDataEnabled = this.azureStorageOptions.FetchLargeMessagesAutomatically, - ThrowExceptionOnInvalidDedupeStatus = true, - }; - - // When running on App Service VMSS stamps, these environment variables are the best way - // to enure unqique worker names - string stamp = this.nameResolver.Resolve("WEBSITE_CURRENT_STAMPNAME"); - string roleInstance = this.nameResolver.Resolve("RoleInstanceId"); - if (!string.IsNullOrEmpty(stamp) && !string.IsNullOrEmpty(roleInstance)) - { - settings.WorkerId = $"{stamp}:{roleInstance}"; - } - - if (!string.IsNullOrEmpty(this.azureStorageOptions.TrackingStoreNamePrefix)) - { - settings.TrackingStoreNamePrefix = this.azureStorageOptions.TrackingStoreNamePrefix; - } - - return settings; - } - - private static StorageAccountDetails GetStorageAccountDetailsOrNull(IConnectionStringResolver connectionStringResolver, string connectionName) - { - if (string.IsNullOrEmpty(connectionName)) - { - return null; - } - - string resolvedStorageConnectionString = connectionStringResolver.Resolve(connectionName); - if (string.IsNullOrEmpty(resolvedStorageConnectionString)) - { - throw new InvalidOperationException($"Unable to resolve the Azure Storage connection named '{connectionName}'."); - } - - return new StorageAccountDetails - { - ConnectionString = resolvedStorageConnectionString, - }; - } - } - } +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using System; +using DurableTask.AzureStorage; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Newtonsoft.Json; + +namespace Microsoft.Azure.WebJobs.Extensions.DurableTask +{ + internal class AzureStorageDurabilityProviderFactory : IDurabilityProviderFactory + { + private readonly DurableTaskOptions options; + private readonly AzureStorageOptions azureStorageOptions; + private readonly IConnectionStringResolver connectionStringResolver; + private readonly string defaultConnectionName; + private readonly INameResolver nameResolver; + private readonly ILoggerFactory loggerFactory; + private AzureStorageDurabilityProvider defaultStorageProvider; + + // Must wait to get settings until we have validated taskhub name. + private bool hasValidatedOptions; + private AzureStorageOrchestrationServiceSettings defaultSettings; + + public AzureStorageDurabilityProviderFactory( + IOptions options, + IConnectionStringResolver connectionStringResolver, + INameResolver nameResolver, + ILoggerFactory loggerFactory) + { + this.options = options.Value; + this.nameResolver = nameResolver; + this.loggerFactory = loggerFactory; + this.azureStorageOptions = new AzureStorageOptions(); + JsonConvert.PopulateObject(JsonConvert.SerializeObject(this.options.StorageProvider), this.azureStorageOptions); + + this.azureStorageOptions.Validate(); + + this.connectionStringResolver = connectionStringResolver ?? throw new ArgumentNullException(nameof(connectionStringResolver)); + this.defaultConnectionName = this.azureStorageOptions.ConnectionStringName ?? ConnectionStringNames.Storage; + } + + internal string GetDefaultStorageConnectionString() + { + return this.connectionStringResolver.Resolve(this.defaultConnectionName); + } + + // This method should not be called before the app settings are resolved into the options. + // Because of this, we wait to validate the options until right before building a durability provider, rather + // than in the Factory constructor. + private void EnsureInitialized() + { + if (!this.hasValidatedOptions) + { + if (!this.options.IsDefaultHubName()) + { + this.azureStorageOptions.ValidateHubName(this.options.HubName); + } + else if (!this.azureStorageOptions.IsSanitizedHubName(this.options.HubName, out string sanitizedHubName)) + { + this.options.SetDefaultHubName(sanitizedHubName); + } + + this.defaultSettings = this.GetAzureStorageOrchestrationServiceSettings(); + this.hasValidatedOptions = true; + } + } + + public virtual DurabilityProvider GetDurabilityProvider() + { + this.EnsureInitialized(); + if (this.defaultStorageProvider == null) + { + var defaultService = new AzureStorageOrchestrationService(this.defaultSettings); + this.defaultStorageProvider = new AzureStorageDurabilityProvider( + defaultService, + this.defaultConnectionName, + this.azureStorageOptions); + } + + return this.defaultStorageProvider; + } + + public virtual DurabilityProvider GetDurabilityProvider(DurableClientAttribute attribute) + { + this.EnsureInitialized(); + return this.GetAzureStorageStorageProvider(attribute); + } + + private AzureStorageDurabilityProvider GetAzureStorageStorageProvider(DurableClientAttribute attribute) + { + string connectionName = attribute.ConnectionName ?? this.defaultConnectionName; + AzureStorageOrchestrationServiceSettings settings = this.GetAzureStorageOrchestrationServiceSettings(connectionName, attribute.TaskHub); + + AzureStorageDurabilityProvider innerClient; + if (string.Equals(this.defaultSettings.TaskHubName, settings.TaskHubName, StringComparison.OrdinalIgnoreCase) && + string.Equals(this.defaultSettings.StorageConnectionString, settings.StorageConnectionString, StringComparison.OrdinalIgnoreCase)) + { + // It's important that clients use the same AzureStorageOrchestrationService instance + // as the host when possible to ensure we any send operations can be picked up + // immediately instead of waiting for the next queue polling interval. + innerClient = this.defaultStorageProvider; + } + else + { + innerClient = new AzureStorageDurabilityProvider( + new AzureStorageOrchestrationService(settings), + connectionName, + this.azureStorageOptions); + } + + return innerClient; + } + + internal AzureStorageOrchestrationServiceSettings GetAzureStorageOrchestrationServiceSettings( + string connectionName = null, + string taskHubNameOverride = null) + { + connectionName = connectionName ?? this.defaultConnectionName; + + string resolvedStorageConnectionString = this.connectionStringResolver.Resolve(connectionName); + + if (string.IsNullOrEmpty(resolvedStorageConnectionString)) + { + throw new InvalidOperationException("Unable to find an Azure Storage connection string to use for this binding."); + } + + TimeSpan extendedSessionTimeout = TimeSpan.FromSeconds( + Math.Max(this.options.ExtendedSessionIdleTimeoutInSeconds, 0)); + + var settings = new AzureStorageOrchestrationServiceSettings + { + StorageConnectionString = resolvedStorageConnectionString, + TaskHubName = taskHubNameOverride ?? this.options.HubName, + PartitionCount = this.azureStorageOptions.PartitionCount, + ControlQueueBatchSize = this.azureStorageOptions.ControlQueueBatchSize, + ControlQueueBufferThreshold = this.azureStorageOptions.ControlQueueBufferThreshold, + ControlQueueVisibilityTimeout = this.azureStorageOptions.ControlQueueVisibilityTimeout, + WorkItemQueueVisibilityTimeout = this.azureStorageOptions.WorkItemQueueVisibilityTimeout, + MaxConcurrentTaskOrchestrationWorkItems = this.options.MaxConcurrentOrchestratorFunctions, + MaxConcurrentTaskActivityWorkItems = this.options.MaxConcurrentActivityFunctions, + ExtendedSessionsEnabled = this.options.ExtendedSessionsEnabled, + ExtendedSessionIdleTimeout = extendedSessionTimeout, + MaxQueuePollingInterval = this.azureStorageOptions.MaxQueuePollingInterval, + TrackingStoreStorageAccountDetails = GetStorageAccountDetailsOrNull( + this.connectionStringResolver, + this.azureStorageOptions.TrackingStoreConnectionStringName), + FetchLargeMessageDataEnabled = this.azureStorageOptions.FetchLargeMessagesAutomatically, + ThrowExceptionOnInvalidDedupeStatus = true, + UseAppLease = this.options.UseAppLease, + AppLeaseOptions = this.options.AppLeaseOptions, + AppName = EndToEndTraceHelper.LocalAppName, + LoggerFactory = this.loggerFactory, + UseLegacyPartitionManagement = this.azureStorageOptions.UseLegacyPartitionManagement, + }; + + // When running on App Service VMSS stamps, these environment variables are the best way + // to enure unqique worker names + string stamp = this.nameResolver.Resolve("WEBSITE_CURRENT_STAMPNAME"); + string roleInstance = this.nameResolver.Resolve("RoleInstanceId"); + if (!string.IsNullOrEmpty(stamp) && !string.IsNullOrEmpty(roleInstance)) + { + settings.WorkerId = $"{stamp}:{roleInstance}"; + } + + if (!string.IsNullOrEmpty(this.azureStorageOptions.TrackingStoreNamePrefix)) + { + settings.TrackingStoreNamePrefix = this.azureStorageOptions.TrackingStoreNamePrefix; + } + + return settings; + } + + private static StorageAccountDetails GetStorageAccountDetailsOrNull(IConnectionStringResolver connectionStringResolver, string connectionName) + { + if (string.IsNullOrEmpty(connectionName)) + { + return null; + } + + string resolvedStorageConnectionString = connectionStringResolver.Resolve(connectionName); + if (string.IsNullOrEmpty(resolvedStorageConnectionString)) + { + throw new InvalidOperationException($"Unable to resolve the Azure Storage connection named '{connectionName}'."); + } + + return new StorageAccountDetails + { + ConnectionString = resolvedStorageConnectionString, + }; + } + } + } diff --git a/src/WebJobs.Extensions.DurableTask/ContextImplementations/DurableActivityContext.cs b/src/WebJobs.Extensions.DurableTask/ContextImplementations/DurableActivityContext.cs index b85547f4c..1de80aade 100644 --- a/src/WebJobs.Extensions.DurableTask/ContextImplementations/DurableActivityContext.cs +++ b/src/WebJobs.Extensions.DurableTask/ContextImplementations/DurableActivityContext.cs @@ -55,13 +55,14 @@ internal JToken GetInputAsJson() { if (this.serializedInput != null && this.parsedJsonInput == null) { - JArray array = JArray.Parse(this.serializedInput); - if (array?.Count != 1) + var objectArray = this.messageDataConverter.Deserialize(this.serializedInput); + + if (objectArray?.Length != 1) { - throw new ArgumentException("The serialized input is expected to be a JSON array with one element."); + throw new ArgumentException("The serialized input is expected to be an object array with one element."); } - this.parsedJsonInput = array[0]; + this.parsedJsonInput = MessagePayloadDataConverter.ConvertToJToken(this.messageDataConverter.Serialize(objectArray[0])); } return this.parsedJsonInput; diff --git a/src/WebJobs.Extensions.DurableTask/ContextImplementations/DurableClient.cs b/src/WebJobs.Extensions.DurableTask/ContextImplementations/DurableClient.cs index 9ba0c089c..496b566fc 100644 --- a/src/WebJobs.Extensions.DurableTask/ContextImplementations/DurableClient.cs +++ b/src/WebJobs.Extensions.DurableTask/ContextImplementations/DurableClient.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Net.Http; using System.Threading; @@ -64,6 +65,10 @@ internal DurableClient( /// string IDurableOrchestrationClient.TaskHubName => this.TaskHubName; + string IDurableClient.TaskHubName => this.TaskHubName; + + string IDurableEntityClient.TaskHubName => this.TaskHubName; + /// HttpResponseMessage IDurableOrchestrationClient.CreateCheckStatusResponse(HttpRequestMessage request, string instanceId, bool returnInternalServerErrorOnFailure) { @@ -85,18 +90,18 @@ HttpManagementPayload IDurableOrchestrationClient.CreateHttpManagementPayload(st } /// - async Task IDurableOrchestrationClient.WaitForCompletionOrCreateCheckStatusResponseAsync(HttpRequestMessage request, string instanceId, TimeSpan timeout, TimeSpan retryInterval) + async Task IDurableOrchestrationClient.WaitForCompletionOrCreateCheckStatusResponseAsync(HttpRequestMessage request, string instanceId, TimeSpan? timeout, TimeSpan? retryInterval) { return await this.WaitForCompletionOrCreateCheckStatusResponseAsync( request, instanceId, this.attribute, - timeout, - retryInterval); + timeout ?? TimeSpan.FromSeconds(10), + retryInterval ?? TimeSpan.FromSeconds(1)); } /// - async Task IDurableOrchestrationClient.WaitForCompletionOrCreateCheckStatusResponseAsync(HttpRequest request, string instanceId, TimeSpan timeout, TimeSpan retryInterval) + async Task IDurableOrchestrationClient.WaitForCompletionOrCreateCheckStatusResponseAsync(HttpRequest request, string instanceId, TimeSpan? timeout, TimeSpan? retryInterval) { HttpRequestMessage requestMessage = ConvertHttpRequestMessage(request); HttpResponseMessage responseMessage = await ((IDurableOrchestrationClient)this).WaitForCompletionOrCreateCheckStatusResponseAsync(requestMessage, instanceId, timeout, retryInterval); @@ -260,6 +265,12 @@ Task IDurableEntityClient.SignalEntityAsync(EntityId entityId, DateTime schedule private async Task SignalEntityAsyncInternal(DurableClient durableClient, string hubName, EntityId entityId, DateTime? scheduledTimeUtc, string operationName, object operationInput) { + var entityKey = entityId.EntityKey; + if (entityKey.Any(IsInvalidCharacter)) + { + throw new ArgumentException(nameof(entityKey), "Entity keys must not contain /, \\, #, ?, or control characters."); + } + if (operationName == null) { throw new ArgumentNullException(nameof(operationName)); @@ -313,14 +324,7 @@ private bool TaskHubMatchesCurrentApp(DurableClient client) private bool ConnectionNameMatchesCurrentApp(DurableClient client) { - var storageProvider = this.config.Options.StorageProvider; - if (storageProvider.TryGetValue("ConnectionStringName", out object connectionName)) - { - var newConnectionName = client.DurabilityProvider.ConnectionName; - return newConnectionName.Equals(connectionName); - } - - return false; + return this.DurabilityProvider.ConnectionNameMatches(client.DurabilityProvider); } /// @@ -386,13 +390,7 @@ async Task IDurableOrchestrationClient.GetStatusAsyn } /// - async Task> IDurableOrchestrationClient.GetStatusAsync(CancellationToken cancellationToken) - { - return await this.GetAllStatusHelper(null, null, null, cancellationToken); - } - - /// - async Task> IDurableOrchestrationClient.GetStatusAsync(DateTime createdTimeFrom, DateTime? createdTimeTo, IEnumerable runtimeStatus, CancellationToken cancellationToken) + async Task> IDurableOrchestrationClient.GetStatusAsync(DateTime? createdTimeFrom, DateTime? createdTimeTo, IEnumerable runtimeStatus, CancellationToken cancellationToken) { return await this.GetAllStatusHelper(createdTimeFrom, createdTimeTo, runtimeStatus, cancellationToken); } @@ -614,7 +612,7 @@ private static async Task GetDurableOrchestrationSta string history = await client.GetOrchestrationHistoryAsync(orchestrationState.OrchestrationInstance); if (!string.IsNullOrEmpty(history)) { - historyArray = JArray.Parse(history); + historyArray = MessagePayloadDataConverter.ConvertToJArray(history); var eventMapper = new Dictionary(); var indexList = new List(); @@ -773,7 +771,7 @@ internal static JToken ParseToJToken(string value) try { - return JToken.Parse(value); + return MessagePayloadDataConverter.ConvertToJToken(value); } catch (JsonReaderException) { @@ -792,6 +790,62 @@ private static void ConvertOutputToJToken(JObject jsonObject, bool showHistoryOu jsonObject["Result"] = ParseToJToken((string)jsonObject["Result"]); } + /// + Task IDurableOrchestrationClient.StartNewAsync(string orchestratorFunctionName, string instanceId) + { + return ((IDurableOrchestrationClient)this).StartNewAsync(orchestratorFunctionName, instanceId, null); + } + + /// + Task IDurableOrchestrationClient.StartNewAsync(string orchestratorFunctionName, T input) + { + return ((IDurableOrchestrationClient)this).StartNewAsync(orchestratorFunctionName, string.Empty, input); + } + + /// + Task IDurableEntityClient.SignalEntityAsync(string entityKey, Action operation) + { + return ((IDurableEntityClient)this).SignalEntityAsync(new EntityId(DurableEntityProxyHelpers.ResolveEntityName(), entityKey), operation); + } + + /// + Task IDurableEntityClient.SignalEntityAsync(string entityKey, DateTime scheduledTimeUtc, Action operation) + { + return ((IDurableEntityClient)this).SignalEntityAsync(new EntityId(DurableEntityProxyHelpers.ResolveEntityName(), entityKey), scheduledTimeUtc, operation); + } + + /// + Task IDurableEntityClient.SignalEntityAsync(EntityId entityId, Action operation) + { + var proxyContext = new EntityClientProxy(this); + var proxy = EntityProxyFactory.Create(proxyContext, entityId); + + operation(proxy); + + if (proxyContext.SignalTask == null) + { + throw new InvalidOperationException("The operation action must perform an operation on the entity"); + } + + return proxyContext.SignalTask; + } + + /// + Task IDurableEntityClient.SignalEntityAsync(EntityId entityId, DateTime scheduledTimeUtc, Action operation) + { + var proxyContext = new EntityClientProxy(this, scheduledTimeUtc); + var proxy = EntityProxyFactory.Create(proxyContext, entityId); + + operation(proxy); + + if (proxyContext.SignalTask == null) + { + throw new InvalidOperationException("The operation action must perform an operation on the entity"); + } + + return proxyContext.SignalTask; + } + private class EventIndexDateMapping { public int Index { get; set; } diff --git a/src/WebJobs.Extensions.DurableTask/ContextImplementations/DurableEntityContext.cs b/src/WebJobs.Extensions.DurableTask/ContextImplementations/DurableEntityContext.cs index ffdaebfd9..159b76ed9 100644 --- a/src/WebJobs.Extensions.DurableTask/ContextImplementations/DurableEntityContext.cs +++ b/src/WebJobs.Extensions.DurableTask/ContextImplementations/DurableEntityContext.cs @@ -564,6 +564,29 @@ internal void SendOutbox(OrchestrationContext innerContext, bool writeBackSucces } } + /// + void IDurableEntityContext.SignalEntity(string entityKey, Action operation) + { + ((IDurableEntityContext)this).SignalEntity(new EntityId(DurableEntityProxyHelpers.ResolveEntityName(), entityKey), operation); + } + + /// + void IDurableEntityContext.SignalEntity(string entityKey, DateTime scheduledTimeUtc, Action operation) + { + ((IDurableEntityContext)this).SignalEntity(new EntityId(DurableEntityProxyHelpers.ResolveEntityName(), entityKey), scheduledTimeUtc, operation); + } + + /// + void IDurableEntityContext.SignalEntity(EntityId entityId, Action operation) + { + operation(EntityProxyFactory.Create(new EntityContextProxy(this), entityId)); + } + + void IDurableEntityContext.SignalEntity(EntityId entityId, DateTime scheduledTimeUtc, Action operation) + { + operation(EntityProxyFactory.Create(new EntityContextProxy(this, scheduledTimeUtc), entityId)); + } + private abstract class OutgoingMessage { } diff --git a/src/WebJobs.Extensions.DurableTask/ContextImplementations/DurableOrchestrationContext.cs b/src/WebJobs.Extensions.DurableTask/ContextImplementations/DurableOrchestrationContext.cs index 38f9a96c9..289b51ffe 100644 --- a/src/WebJobs.Extensions.DurableTask/ContextImplementations/DurableOrchestrationContext.cs +++ b/src/WebJobs.Extensions.DurableTask/ContextImplementations/DurableOrchestrationContext.cs @@ -85,6 +85,8 @@ internal bool IsReplaying internal bool IsCompleted { get; set; } + internal bool IsLongRunningTimer { get; private set; } + internal ExceptionDispatchInfo OrchestrationException { get; set; } internal bool IsOutputSet => this.serializedOutput != null; @@ -134,7 +136,7 @@ internal string GetRawInput() /// internal JToken GetInputAsJson() { - return this.RawInput != null ? JToken.Parse(this.RawInput) : null; + return MessagePayloadDataConverter.ConvertToJToken(this.RawInput); } /// @@ -208,6 +210,11 @@ internal string GetSerializedCustomStatus() return this.serializedCustomStatus; } + Task IDurableOrchestrationContext.CallSubOrchestratorAsync(string functionName, object input) + { + return ((IDurableOrchestrationContext)this).CallSubOrchestratorAsync(functionName, string.Empty, input); + } + /// Task IDurableOrchestrationContext.CallSubOrchestratorAsync(string functionName, string instanceId, object input) { @@ -292,7 +299,6 @@ private DurableHttpRequest CreateLocationPollRequest(DurableHttpRequest durableH method: HttpMethod.Get, uri: new Uri(locationUri), headers: durableHttpRequest.Headers, - content: durableHttpRequest.Content, tokenSource: durableHttpRequest.TokenSource); // Do not copy over the x-functions-key header, as in many cases, the @@ -308,22 +314,62 @@ async Task IDurableOrchestrationContext.CreateTimer(DateTime fireAt, T sta { this.ThrowIfInvalidAccess(); - if (!this.durabilityProvider.ValidateDelayTime(fireAt.Subtract(this.InnerContext.CurrentUtcDateTime), out string errorMessage)) + DateTime intervalFireAt = fireAt; + + if (fireAt.Subtract(this.InnerContext.CurrentUtcDateTime) > this.durabilityProvider.MaximumDelayTime) { - throw new ArgumentException(errorMessage, nameof(fireAt)); + this.IsLongRunningTimer = true; + intervalFireAt = this.InnerContext.CurrentUtcDateTime.Add(this.durabilityProvider.LongRunningTimerIntervalLength); } - this.IncrementActionsOrThrowException(); - Task timerTask = this.InnerContext.CreateTimer(fireAt, state, cancelToken); + T result = default; - this.Config.TraceHelper.FunctionListening( - this.Config.Options.HubName, - this.OrchestrationName, - this.InstanceId, - reason: $"CreateTimer:{fireAt:o}", - isReplay: this.InnerContext.IsReplaying); + if (!this.IsLongRunningTimer) + { + this.IncrementActionsOrThrowException(); + Task timerTask = this.InnerContext.CreateTimer(fireAt, state, cancelToken); - T result = await timerTask; + this.Config.TraceHelper.FunctionListening( + this.Config.Options.HubName, + this.OrchestrationName, + this.InstanceId, + reason: $"CreateTimer:{fireAt:o}", + isReplay: this.InnerContext.IsReplaying); + + result = await timerTask; + } + else + { + this.Config.TraceHelper.FunctionListening( + this.Config.Options.HubName, + this.OrchestrationName, + this.InstanceId, + reason: $"CreateTimer:{fireAt:o}", + isReplay: this.InnerContext.IsReplaying); + + while (this.InnerContext.CurrentUtcDateTime < fireAt && !cancelToken.IsCancellationRequested) + { + this.IncrementActionsOrThrowException(); + Task timerTask = this.InnerContext.CreateTimer(intervalFireAt, state, cancelToken); + + result = await timerTask; + + TimeSpan remainingTime = fireAt.Subtract(this.InnerContext.CurrentUtcDateTime); + + if (remainingTime <= TimeSpan.Zero) + { + break; + } + else if (remainingTime < this.durabilityProvider.LongRunningTimerIntervalLength) + { + intervalFireAt = this.InnerContext.CurrentUtcDateTime.Add(remainingTime); + } + else + { + intervalFireAt = this.InnerContext.CurrentUtcDateTime.Add(this.durabilityProvider.LongRunningTimerIntervalLength); + } + } + } this.Config.TraceHelper.TimerExpired( this.Config.Options.HubName, @@ -332,9 +378,33 @@ async Task IDurableOrchestrationContext.CreateTimer(DateTime fireAt, T sta expirationTime: fireAt, isReplay: this.InnerContext.IsReplaying); + this.IsLongRunningTimer = false; + return result; } + internal Task OutOfProcCreateTimer(DurableOrchestrationContext ctx, DateTime fireAt, CancellationToken cancelToken) + { + if (!this.ValidOutOfProcTimer(fireAt, out string errorMessage)) + { + throw new ArgumentException(errorMessage, nameof(fireAt)); + } + + return ((IDurableOrchestrationContext)ctx).CreateTimer(fireAt, cancelToken); + } + + private bool ValidOutOfProcTimer(DateTime fireAt, out string errorMessage) + { + this.ThrowIfInvalidAccess(); + + if (!this.durabilityProvider.ValidateDelayTime(fireAt.Subtract(this.InnerContext.CurrentUtcDateTime), out errorMessage)) + { + return false; + } + + return true; + } + /// Task IDurableOrchestrationContext.WaitForExternalEvent(string name) { @@ -865,7 +935,7 @@ internal void RescheduleBufferedExternalEvents() { // Need to round-trip serialization since SendEvent always tries to serialize. string rawInput = events.Dequeue(); - JToken jsonData = JToken.Parse(rawInput); + JToken jsonData = MessagePayloadDataConverter.ConvertToJToken(rawInput); this.InnerContext.SendEvent(instance, eventName, jsonData); } } @@ -882,7 +952,7 @@ private Task WaitForExternalEvent(string name, TimeSpan timeout, Action(name, "ExternalEvent"); waitForEventTask.ContinueWith( @@ -1112,6 +1182,90 @@ private Guid NewGuid() return GuidManager.CreateDeterministicGuid(GuidManager.UrlNamespaceValue, guidNameValue); } + /// + Task IDurableOrchestrationContext.CallEntityAsync(EntityId entityId, string operationName) + { + return ((IDurableOrchestrationContext)this).CallEntityAsync(entityId, operationName, null); + } + + /// + Task IDurableOrchestrationContext.CallEntityAsync(EntityId entityId, string operationName) + { + return ((IDurableOrchestrationContext)this).CallEntityAsync(entityId, operationName, null); + } + + /// + Task IDurableOrchestrationContext.CallSubOrchestratorAsync(string functionName, object input) + { + return ((IDurableOrchestrationContext)this).CallSubOrchestratorAsync(functionName, input); + } + + /// + Task IDurableOrchestrationContext.CallSubOrchestratorAsync(string functionName, string instanceId, object input) + { + return ((IDurableOrchestrationContext)this).CallSubOrchestratorAsync(functionName, instanceId, input); + } + + /// + Task IDurableOrchestrationContext.CallSubOrchestratorWithRetryAsync(string functionName, RetryOptions retryOptions, object input) + { + return ((IDurableOrchestrationContext)this).CallSubOrchestratorWithRetryAsync(functionName, retryOptions, input); + } + + /// + Task IDurableOrchestrationContext.CallSubOrchestratorWithRetryAsync(string functionName, RetryOptions retryOptions, string instanceId, object input) + { + return ((IDurableOrchestrationContext)this).CallSubOrchestratorWithRetryAsync(functionName, retryOptions, instanceId, input); + } + + /// + Task IDurableOrchestrationContext.CallSubOrchestratorWithRetryAsync(string functionName, RetryOptions retryOptions, object input) + { + return ((IDurableOrchestrationContext)this).CallSubOrchestratorWithRetryAsync(functionName, retryOptions, null, input); + } + + /// + Task IDurableOrchestrationContext.CreateTimer(DateTime fireAt, CancellationToken cancelToken) + { + return ((IDurableOrchestrationContext)this).CreateTimer(fireAt, null, cancelToken); + } + + /// + Task IDurableOrchestrationContext.WaitForExternalEvent(string name) + { + return ((IDurableOrchestrationContext)this).WaitForExternalEvent(name); + } + + /// + Task IDurableOrchestrationContext.WaitForExternalEvent(string name, TimeSpan timeout, CancellationToken cancelToken) + { + return ((IDurableOrchestrationContext)this).WaitForExternalEvent(name, timeout, cancelToken); + } + + /// + Task IDurableOrchestrationContext.CallActivityAsync(string functionName, object input) + { + return ((IDurableOrchestrationContext)this).CallActivityAsync(functionName, input); + } + + /// + Task IDurableOrchestrationContext.CallActivityWithRetryAsync(string functionName, RetryOptions retryOptions, object input) + { + return ((IDurableOrchestrationContext)this).CallActivityWithRetryAsync(functionName, retryOptions, input); + } + + /// + TEntityInterface IDurableOrchestrationContext.CreateEntityProxy(string entityKey) + { + return ((IDurableOrchestrationContext)this).CreateEntityProxy(new EntityId(DurableEntityProxyHelpers.ResolveEntityName(), entityKey)); + } + + /// + TEntityInterface IDurableOrchestrationContext.CreateEntityProxy(EntityId entityId) + { + return EntityProxyFactory.Create(new OrchestrationContextProxy(this), entityId); + } + private class LockReleaser : IDisposable { private readonly DurableOrchestrationContext context; diff --git a/src/WebJobs.Extensions.DurableTask/ContextInterfaces/DurableContextExtensions.cs b/src/WebJobs.Extensions.DurableTask/ContextInterfaces/DurableContextExtensions.cs index d8a094b7b..273f2773d 100644 --- a/src/WebJobs.Extensions.DurableTask/ContextInterfaces/DurableContextExtensions.cs +++ b/src/WebJobs.Extensions.DurableTask/ContextInterfaces/DurableContextExtensions.cs @@ -1,15 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. -using System; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; using Microsoft.Extensions.Logging; -#if !FUNCTIONS_V1 -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -#endif namespace Microsoft.Azure.WebJobs.Extensions.DurableTask { @@ -18,544 +10,6 @@ namespace Microsoft.Azure.WebJobs.Extensions.DurableTask /// public static class DurableContextExtensions { - /// - /// Schedules an activity function named for execution. - /// - /// The context object. - /// The name of the activity function to call. - /// The JSON-serializeable input to pass to the activity function. - /// A durable task that completes when the called function completes or fails. - /// - /// The specified function does not exist, is disabled, or is not an orchestrator function. - /// - /// - /// The current thread is different than the thread which started the orchestrator execution. - /// - /// - /// The activity function failed with an unhandled exception. - /// - public static Task CallActivityAsync(this IDurableOrchestrationContext context, string functionName, object input) - { - return context.CallActivityAsync(functionName, input); - } - - /// - /// Schedules an activity function named for execution with retry options. - /// - /// The context object. - /// The name of the activity function to call. - /// The retry option for the activity function. - /// The JSON-serializeable input to pass to the activity function. - /// A durable task that completes when the called activity function completes or fails. - /// - /// The retry option object is null. - /// - /// - /// The specified function does not exist, is disabled, or is not an orchestrator function. - /// - /// - /// The current thread is different than the thread which started the orchestrator execution. - /// - /// - /// The activity function failed with an unhandled exception. - /// - public static Task CallActivityWithRetryAsync(this IDurableOrchestrationContext context, string functionName, RetryOptions retryOptions, object input) - { - return context.CallActivityWithRetryAsync(functionName, retryOptions, input); - } - - /// - /// Schedules an orchestrator function named for execution. - /// - /// The context object. - /// The name of the orchestrator function to call. - /// The JSON-serializeable input to pass to the orchestrator function. - /// A durable task that completes when the called orchestrator function completes or fails. - /// - /// The specified function does not exist, is disabled, or is not an orchestrator function. - /// - /// - /// The current thread is different than the thread which started the orchestrator execution. - /// - /// - /// The sub-orchestrator function failed with an unhandled exception. - /// - public static Task CallSubOrchestratorAsync(this IDurableOrchestrationContext context, string functionName, object input) - { - return context.CallSubOrchestratorAsync(functionName, input); - } - - /// - /// Schedules an orchestrator function named for execution. - /// - /// The context object. - /// The name of the orchestrator function to call. - /// A unique ID to use for the sub-orchestration instance. - /// The JSON-serializeable input to pass to the orchestrator function. - /// A durable task that completes when the called orchestrator function completes or fails. - /// - /// The specified function does not exist, is disabled, or is not an orchestrator function. - /// - /// - /// The current thread is different than the thread which started the orchestrator execution. - /// - /// - /// The activity function failed with an unhandled exception. - /// - public static Task CallSubOrchestratorAsync(this IDurableOrchestrationContext context, string functionName, string instanceId, object input) - { - return context.CallSubOrchestratorAsync(functionName, instanceId, input); - } - - /// - /// Schedules an orchestration function named for execution. - /// - /// The return type of the scheduled orchestrator function. - /// The context object. - /// The name of the orchestrator function to call. - /// The JSON-serializeable input to pass to the orchestrator function. - /// A durable task that completes when the called orchestrator function completes or fails. - /// - /// The specified function does not exist, is disabled, or is not an orchestrator function. - /// - /// - /// The current thread is different than the thread which started the orchestrator execution. - /// - /// - /// The activity function failed with an unhandled exception. - /// - public static Task CallSubOrchestratorAsync(this IDurableOrchestrationContext context, string functionName, object input) - { - return context.CallSubOrchestratorAsync(functionName, null, input); - } - - /// - /// Schedules an orchestrator function named for execution with retry options. - /// - /// The context object. - /// The name of the orchestrator function to call. - /// The retry option for the orchestrator function. - /// The JSON-serializeable input to pass to the orchestrator function. - /// A durable task that completes when the called orchestrator function completes or fails. - /// - /// The retry option object is null. - /// - /// - /// The specified function does not exist, is disabled, or is not an orchestrator function. - /// - /// - /// The current thread is different than the thread which started the orchestrator execution. - /// - /// - /// The activity function failed with an unhandled exception. - /// - public static Task CallSubOrchestratorWithRetryAsync(this IDurableOrchestrationContext context, string functionName, RetryOptions retryOptions, object input) - { - return context.CallSubOrchestratorWithRetryAsync(functionName, retryOptions, null, input); - } - - /// - /// Schedules an orchestrator function named for execution with retry options. - /// - /// The context object. - /// The name of the orchestrator function to call. - /// The retry option for the orchestrator function. - /// A unique ID to use for the sub-orchestration instance. - /// The JSON-serializeable input to pass to the orchestrator function. - /// A durable task that completes when the called orchestrator function completes or fails. - /// - /// The retry option object is null. - /// - /// - /// The specified function does not exist, is disabled, or is not an orchestrator function. - /// - /// - /// The current thread is different than the thread which started the orchestrator execution. - /// - /// - /// The activity function failed with an unhandled exception. - /// - public static Task CallSubOrchestratorWithRetryAsync(this IDurableOrchestrationContext context, string functionName, RetryOptions retryOptions, string instanceId, object input) - { - return context.CallSubOrchestratorWithRetryAsync(functionName, retryOptions, instanceId, input); - } - - /// - /// Schedules an orchestrator function named for execution with retry options. - /// - /// The return type of the scheduled orchestrator function. - /// The context object. - /// The name of the orchestrator function to call. - /// The retry option for the orchestrator function. - /// The JSON-serializeable input to pass to the orchestrator function. - /// A durable task that completes when the called orchestrator function completes or fails. - /// - /// The retry option object is null. - /// - /// - /// The specified function does not exist, is disabled, or is not an orchestrator function. - /// - /// - /// The current thread is different than the thread which started the orchestrator execution. - /// - /// - /// The activity function failed with an unhandled exception. - /// - public static Task CallSubOrchestratorWithRetryAsync(this IDurableOrchestrationContext context, string functionName, RetryOptions retryOptions, object input) - { - return context.CallSubOrchestratorWithRetryAsync(functionName, retryOptions, null, input); - } - - /// - /// Creates a durable timer that expires at a specified time. - /// - /// - /// All durable timers created using this method must either expire or be cancelled - /// using the before the orchestrator function completes. - /// Otherwise the underlying framework will keep the instance alive until the timer expires. - /// - /// The context object. - /// The time at which the timer should expire. - /// The CancellationToken to use for cancelling the timer. - /// A durable task that completes when the durable timer expires. - public static Task CreateTimer(this IDurableOrchestrationContext context, DateTime fireAt, CancellationToken cancelToken) - { - return context.CreateTimer(fireAt, null, cancelToken); - } - - /// - /// Waits asynchronously for an event to be raised with name . - /// - /// - /// External clients can raise events to a waiting orchestration instance using - /// with the object parameter set to null. - /// - /// The context object. - /// The name of the event to wait for. - /// A durable task that completes when the external event is received. - public static Task WaitForExternalEvent(this IDurableOrchestrationContext context, string name) - { - return context.WaitForExternalEvent(name); - } - - /// - /// Waits asynchronously for an event to be raised with name . - /// - /// - /// External clients can raise events to a waiting orchestration instance using - /// with the object parameter set to null. - /// - /// The context object. - /// The name of the event to wait for. - /// The duration after which to throw a TimeoutException. - /// A durable task that completes when the external event is received. - /// - /// The external event was not received before the timeout expired. - /// - public static Task WaitForExternalEvent(this IDurableOrchestrationContext context, string name, TimeSpan timeout) - { - return context.WaitForExternalEvent(name, timeout); - } - - /// - /// Waits asynchronously for an event to be raised with name . - /// - /// - /// External clients can raise events to a waiting orchestration instance using - /// with the object parameter set to null. - /// - /// The context object. - /// The name of the event to wait for. - /// The duration after which to throw a TimeoutException. - /// The CancellationToken to use for cancelling 's internal timer. - /// A durable task that completes when the external event is received. - /// - /// The external event was not received before the timeout expired. - /// - public static Task WaitForExternalEvent(this IDurableOrchestrationContext context, string name, TimeSpan timeout, CancellationToken cancelToken) - { - return context.WaitForExternalEvent(name, timeout, cancelToken); - } - - /// - /// Waits asynchronously for an event to be raised with name and returns the event data. - /// - /// - /// External clients can raise events to a waiting orchestration instance using - /// . - /// - /// The context object. - /// The name of the event to wait for. - /// The duration after which to throw a TimeoutException. - /// Any serializeable type that represents the JSON event payload. - /// A durable task that completes when the external event is received. - /// - /// The external event was not received before the timeout expired. - /// - public static Task WaitForExternalEvent(this IDurableOrchestrationContext context, string name, TimeSpan timeout) - { - return context.WaitForExternalEvent(name, timeout, CancellationToken.None); - } - - /// - /// Waits asynchronously for an event to be raised with name and returns the event data. - /// - /// - /// External clients can raise events to a waiting orchestration instance using - /// . - /// - /// The context object. - /// The name of the event to wait for. - /// The duration after which to return the value in the parameter. - /// The default value to return if the timeout expires before the external event is received. - /// Any serializeable type that represents the JSON event payload. - /// A durable task that completes when the external event is received, or returns the value of - /// if the timeout expires. - public static Task WaitForExternalEvent(this IDurableOrchestrationContext context, string name, TimeSpan timeout, T defaultValue) - { - return context.WaitForExternalEvent(name, timeout, defaultValue, CancellationToken.None); - } - - /// - /// Calls an operation on an entity and returns the result asynchronously. - /// - /// The JSON-serializable result type of the operation. - /// The context object. - /// The target entity. - /// The name of the operation. - /// A task representing the result of the operation. - public static Task CallEntityAsync(this IDurableOrchestrationContext context, EntityId entityId, string operationName) - { - return context.CallEntityAsync(entityId, operationName, null); - } - - /// - /// Calls an operation on an entity and waits for it to complete. - /// - /// The context object. - /// The target entity. - /// The name of the operation. - /// A task representing the completion of the operation on the entity. - public static Task CallEntityAsync(this IDurableOrchestrationContext context, EntityId entityId, string operationName) - { - return context.CallEntityAsync(entityId, operationName, null); - } - - /// - /// Creates an HTTP response which either contains a payload of management URLs for a non-completed instance - /// or contains the payload containing the output of the completed orchestration. - /// - /// - /// If the orchestration instance completes within the default 10 second timeout, then the HTTP response payload will - /// contain the output of the orchestration instance formatted as JSON. However, if the orchestration does not - /// complete within this timeout, then the HTTP response will be identical to that of the - /// API. - /// - /// The client object. - /// The HTTP request that triggered the current function. - /// The unique ID of the instance to check. - /// An HTTP response which may include a 202 and location header or a 200 with the durable function output in the response body. - public static Task WaitForCompletionOrCreateCheckStatusResponseAsync( - this IDurableOrchestrationClient client, - HttpRequestMessage request, - string instanceId) - { - return client.WaitForCompletionOrCreateCheckStatusResponseAsync( - request, - instanceId, - timeout: TimeSpan.FromSeconds(10)); - } - -#if !FUNCTIONS_V1 - /// - /// Creates an HTTP response which either contains a payload of management URLs for a non-completed instance - /// or contains the payload containing the output of the completed orchestration. - /// - /// - /// If the orchestration instance completes within the default 10 second timeout, then the HTTP response payload will - /// contain the output of the orchestration instance formatted as JSON. However, if the orchestration does not - /// complete within this timeout, then the HTTP response will be identical to that of the - /// API. - /// - /// The client object. - /// The HTTP request that triggered the current function. - /// The unique ID of the instance to check. - /// An HTTP response which may include a 202 and location header or a 200 with the durable function output in the response body. - public static Task WaitForCompletionOrCreateCheckStatusResponseAsync( - this IDurableOrchestrationClient client, - HttpRequest request, - string instanceId) - { - return client.WaitForCompletionOrCreateCheckStatusResponseAsync( - request, - instanceId, - timeout: TimeSpan.FromSeconds(10)); - } -#endif - - /// - /// Creates an HTTP response which either contains a payload of management URLs for a non-completed instance - /// or contains the payload containing the output of the completed orchestration. - /// - /// - /// If the orchestration instance completes within the specified timeout, then the HTTP response payload will - /// contain the output of the orchestration instance formatted as JSON. However, if the orchestration does not - /// complete within the specified timeout, then the HTTP response will be identical to that of the - /// API. - /// - /// The client object. - /// The HTTP request that triggered the current function. - /// The unique ID of the instance to check. - /// Total allowed timeout for output from the durable function. The default value is 10 seconds. - /// An HTTP response which may include a 202 and location header or a 200 with the durable function output in the response body. - public static Task WaitForCompletionOrCreateCheckStatusResponseAsync( - this IDurableOrchestrationClient client, - HttpRequestMessage request, - string instanceId, - TimeSpan timeout) - { - return client.WaitForCompletionOrCreateCheckStatusResponseAsync( - request, - instanceId, - timeout, - retryInterval: TimeSpan.FromSeconds(1)); - } - -#if !FUNCTIONS_V1 - /// - /// Creates an HTTP response which either contains a payload of management URLs for a non-completed instance - /// or contains the payload containing the output of the completed orchestration. - /// - /// - /// If the orchestration instance completes within the specified timeout, then the HTTP response payload will - /// contain the output of the orchestration instance formatted as JSON. However, if the orchestration does not - /// complete within the specified timeout, then the HTTP response will be identical to that of the - /// API. - /// - /// The client object. - /// The HTTP request that triggered the current function. - /// The unique ID of the instance to check. - /// Total allowed timeout for output from the durable function. The default value is 10 seconds. - /// An HTTP response which may include a 202 and location header or a 200 with the durable function output in the response body. - public static Task WaitForCompletionOrCreateCheckStatusResponseAsync( - this IDurableOrchestrationClient client, - HttpRequest request, - string instanceId, - TimeSpan timeout) - { - return client.WaitForCompletionOrCreateCheckStatusResponseAsync( - request, - instanceId, - timeout, - retryInterval: TimeSpan.FromSeconds(1)); - } -#endif - - /// - /// Starts a new execution of the specified orchestrator function. - /// - /// The client object. - /// The name of the orchestrator function to start. - /// The ID to use for the new orchestration instance. - /// A task that completes when the orchestration is started. The task contains the instance id of the started - /// orchestratation instance. - /// - /// The specified function does not exist, is disabled, or is not an orchestrator function. - /// - public static Task StartNewAsync( - this IDurableOrchestrationClient client, - string orchestratorFunctionName, - string instanceId) - { - return client.StartNewAsync(orchestratorFunctionName, instanceId, null); - } - - /// - /// Starts a new execution of the specified orchestrator function. - /// - /// The client object. - /// The name of the orchestrator function to start. - /// JSON-serializeable input value for the orchestrator function. - /// The type of the input value for the orchestrator function. - /// A task that completes when the orchestration is started. The task contains the instance id of the started - /// orchestratation instance. - /// - /// The specified function does not exist, is disabled, or is not an orchestrator function. - /// - public static Task StartNewAsync( - this IDurableOrchestrationClient client, - string orchestratorFunctionName, - T input) - where T : class - { - return client.StartNewAsync(orchestratorFunctionName, string.Empty, input); - } - - /// - /// Starts a new execution of the specified orchestrator function. - /// - /// The client object. - /// The name of the orchestrator function to start. - /// A task that completes when the orchestration is started. The task contains the instance id of the started - /// orchestratation instance. - /// - /// The specified function does not exist, is disabled, or is not an orchestrator function. - /// - public static Task StartNewAsync( - this IDurableOrchestrationClient client, - string orchestratorFunctionName) - { - return client.StartNewAsync(orchestratorFunctionName, null); - } - - /// - /// Sends an event notification message to a waiting orchestration instance. - /// - /// - /// - /// In order to handle the event, the target orchestration instance must be waiting for an - /// event named using the - /// API. - /// - /// - /// The instance id does not corespond to a valid orchestration instance. - /// The orchestration instance with the provided instance id is not running. - /// The client object. - /// The ID of the orchestration instance that will handle the event. - /// The name of the event. - /// A task that completes when the event notification message has been enqueued. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate", Justification = "This method does not work with the .NET Framework event model.")] - public static Task RaiseEventAsync( - this IDurableOrchestrationClient client, - string instanceId, - string eventName) - { - return client.RaiseEventAsync(instanceId, eventName, null); - } - - /// - /// Gets the status of the specified orchestration instance. - /// - /// The client object. - /// The ID of the orchestration instance to query. - /// Returns a task which completes when the status has been fetched. - public static Task GetStatusAsync(this IDurableOrchestrationClient client, string instanceId) - { - return client.GetStatusAsync(instanceId, showHistory: false); - } - - /// - /// Gets the status of the specified orchestration instance. - /// - /// The client object. - /// The ID of the orchestration instance to query. - /// Boolean marker for including execution history in the response. - /// Returns a task which completes when the status has been fetched. - public static Task GetStatusAsync(this IDurableOrchestrationClient client, string instanceId, bool showHistory) - { - return client.GetStatusAsync(instanceId, showHistory, showHistoryOutput: false, showInput: true); - } - /// /// Returns an instance of ILogger that is replay safe, ensuring the logger logs only when the orchestrator /// is not replaying that line of code. diff --git a/src/WebJobs.Extensions.DurableTask/ContextInterfaces/IDurableEntityClient.cs b/src/WebJobs.Extensions.DurableTask/ContextInterfaces/IDurableEntityClient.cs index 720664235..dda4a2908 100644 --- a/src/WebJobs.Extensions.DurableTask/ContextInterfaces/IDurableEntityClient.cs +++ b/src/WebJobs.Extensions.DurableTask/ContextInterfaces/IDurableEntityClient.cs @@ -43,6 +43,44 @@ public interface IDurableEntityClient /// A task that completes when the message has been reliably enqueued. Task SignalEntityAsync(EntityId entityId, DateTime scheduledTimeUtc, string operationName, object operationInput = null, string taskHubName = null, string connectionName = null); + /// + /// Signals an entity to perform an operation. + /// + /// Entity interface. + /// The target entity key. + /// A delegate that performs the desired operation on the entity. + /// A task that completes when the message has been reliably enqueued. + Task SignalEntityAsync(string entityKey, Action operation); + + /// + /// Signals an entity to perform an operation, at a specified time. + /// + /// Entity interface. + /// The target entity key. + /// The time at which to start the operation. + /// A delegate that performs the desired operation on the entity. + /// A task that completes when the message has been reliably enqueued. + Task SignalEntityAsync(string entityKey, DateTime scheduledTimeUtc, Action operation); + + /// + /// Signals an entity to perform an operation. + /// + /// Entity interface. + /// The target entity. + /// A delegate that performs the desired operation on the entity. + /// A task that completes when the message has been reliably enqueued. + Task SignalEntityAsync(EntityId entityId, Action operation); + + /// + /// Signals an entity to perform an operation, at a specified time. + /// + /// Entity interface. + /// The target entity. + /// The time at which to start the operation. + /// A delegate that performs the desired operation on the entity. + /// A task that completes when the message has been reliably enqueued. + Task SignalEntityAsync(EntityId entityId, DateTime scheduledTimeUtc, Action operation); + /// /// Tries to read the current state of an entity. Returns default() if the entity does not /// exist, or if the JSON-serialized state of the entity is larger than 16KB. diff --git a/src/WebJobs.Extensions.DurableTask/ContextInterfaces/IDurableEntityContext.cs b/src/WebJobs.Extensions.DurableTask/ContextInterfaces/IDurableEntityContext.cs index 7fef5f849..b4494c5a6 100644 --- a/src/WebJobs.Extensions.DurableTask/ContextInterfaces/IDurableEntityContext.cs +++ b/src/WebJobs.Extensions.DurableTask/ContextInterfaces/IDurableEntityContext.cs @@ -113,6 +113,40 @@ public interface IDurableEntityContext /// The input for the operation. void SignalEntity(EntityId entity, DateTime scheduledTimeUtc, string operationName, object operationInput = null); + /// + /// Signals an entity to perform an operation. + /// + /// The target entity key. + /// A delegate that performs the desired operation on the entity. + /// Entity interface. + void SignalEntity(string entityKey, Action operation); + + /// + /// Signals an entity to perform an operation, at a specified time. + /// + /// The target entity key. + /// The time at which to start the operation. + /// A delegate that performs the desired operation on the entity. + /// Entity interface. + void SignalEntity(string entityKey, DateTime scheduledTimeUtc, Action operation); + + /// + /// Signals an entity to perform an operation. + /// + /// The target entity. + /// A delegate that performs the desired operation on the entity. + /// Entity interface. + void SignalEntity(EntityId entityId, Action operation); + + /// + /// Signals an entity to perform an operation, at a specified time. + /// + /// The target entity. + /// The time at which to start the operation. + /// A delegate that performs the desired operation on the entity. + /// Entity interface. + void SignalEntity(EntityId entityId, DateTime scheduledTimeUtc, Action operation); + /// /// Schedules a orchestration function named for execution./>. /// Any result or exception is ignored (fire and forget). diff --git a/src/WebJobs.Extensions.DurableTask/ContextInterfaces/IDurableOrchestrationClient.cs b/src/WebJobs.Extensions.DurableTask/ContextInterfaces/IDurableOrchestrationClient.cs index 52a576e78..efbead085 100644 --- a/src/WebJobs.Extensions.DurableTask/ContextInterfaces/IDurableOrchestrationClient.cs +++ b/src/WebJobs.Extensions.DurableTask/ContextInterfaces/IDurableOrchestrationClient.cs @@ -82,8 +82,8 @@ public interface IDurableOrchestrationClient Task WaitForCompletionOrCreateCheckStatusResponseAsync( HttpRequestMessage request, string instanceId, - TimeSpan timeout, - TimeSpan retryInterval); + TimeSpan? timeout = null, + TimeSpan? retryInterval = null); /// /// Creates an HTTP response which either contains a payload of management URLs for a non-completed instance @@ -103,8 +103,38 @@ Task WaitForCompletionOrCreateCheckStatusResponseAsync( Task WaitForCompletionOrCreateCheckStatusResponseAsync( HttpRequest request, string instanceId, - TimeSpan timeout, - TimeSpan retryInterval); + TimeSpan? timeout = null, + TimeSpan? retryInterval = null); + + /// + /// Starts a new execution of the specified orchestrator function. + /// + /// The name of the orchestrator function to start. + /// The ID to use for the new orchestration instance. + /// A task that completes when the orchestration is started. The task contains the instance id of the started + /// orchestratation instance. + /// + /// The specified function does not exist, is disabled, or is not an orchestrator function. + /// + Task StartNewAsync( + string orchestratorFunctionName, + string instanceId = null); + + /// + /// Starts a new execution of the specified orchestrator function. + /// + /// The name of the orchestrator function to start. + /// JSON-serializeable input value for the orchestrator function. + /// The type of the input value for the orchestrator function. + /// A task that completes when the orchestration is started. The task contains the instance id of the started + /// orchestratation instance. + /// + /// The specified function does not exist, is disabled, or is not an orchestrator function. + /// + Task StartNewAsync( + string orchestratorFunctionName, + T input) + where T : class; /// /// Starts a new instance of the specified orchestrator function. @@ -141,7 +171,7 @@ Task WaitForCompletionOrCreateCheckStatusResponseAsync( /// The JSON-serializeable data associated with the event. /// A task that completes when the event notification message has been enqueued. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate", Justification = "This method does not work with the .NET Framework event model.")] - Task RaiseEventAsync(string instanceId, string eventName, object eventData); + Task RaiseEventAsync(string instanceId, string eventName, object eventData = null); /// /// Sends an event notification message to a waiting orchestration instance. @@ -164,7 +194,7 @@ Task WaitForCompletionOrCreateCheckStatusResponseAsync( /// The name of the connection string associated with . /// A task that completes when the event notification message has been enqueued. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate", Justification = "This method does not work with the .NET Framework event model.")] - Task RaiseEventAsync(string taskHubName, string instanceId, string eventName, object eventData, string connectionName = null); + Task RaiseEventAsync(string taskHubName, string instanceId, string eventName, object eventData = null, string connectionName = null); /// /// Terminates a running orchestration instance. @@ -205,26 +235,18 @@ Task WaitForCompletionOrCreateCheckStatusResponseAsync( /// Boolean marker for including input and output in the execution history response. /// If set, fetch and return the input for the orchestration instance. /// Returns a task which completes when the status has been fetched. - Task GetStatusAsync(string instanceId, bool showHistory, bool showHistoryOutput, bool showInput); - - /// - /// Gets all the status of the orchestration instances. - /// - /// Cancellation token that can be used to cancel the status query operation. - /// Returns orchestration status for all instances. - [Obsolete] - Task> GetStatusAsync(CancellationToken cancellationToken = default(CancellationToken)); + Task GetStatusAsync(string instanceId, bool showHistory = false, bool showHistoryOutput = false, bool showInput = true); /// /// Gets the status of all orchestration instances that match the specified conditions. /// - /// Return orchestration instances which were created after this DateTime. - /// Return orchestration instances which were created before this DateTime. - /// Return orchestration instances which matches the runtimeStatus. - /// Cancellation token that can be used to cancel the status query operation. + /// If specified, return orchestration instances which were created after this DateTime. + /// If specified, return orchestration instances which were created before this DateTime. + /// If specified, return orchestration instances which matches the runtimeStatus. + /// If specified, this ancellation token can be used to cancel the status query operation. /// Returns orchestration status for all instances. [Obsolete] - Task> GetStatusAsync(DateTime createdTimeFrom, DateTime? createdTimeTo, IEnumerable runtimeStatus, CancellationToken cancellationToken = default(CancellationToken)); + Task> GetStatusAsync(DateTime? createdTimeFrom = null, DateTime? createdTimeTo = null, IEnumerable runtimeStatus = null, CancellationToken cancellationToken = default(CancellationToken)); /// /// Purge the history for a concrete instance. diff --git a/src/WebJobs.Extensions.DurableTask/ContextInterfaces/IDurableOrchestrationContext.cs b/src/WebJobs.Extensions.DurableTask/ContextInterfaces/IDurableOrchestrationContext.cs index c6371f63c..bff3125f3 100644 --- a/src/WebJobs.Extensions.DurableTask/ContextInterfaces/IDurableOrchestrationContext.cs +++ b/src/WebJobs.Extensions.DurableTask/ContextInterfaces/IDurableOrchestrationContext.cs @@ -124,6 +124,23 @@ public interface IDurableOrchestrationContext /// A Result of the HTTP call. Task CallHttpAsync(DurableHttpRequest req); + /// + /// Calls an operation on an entity and returns the result asynchronously. + /// + /// The JSON-serializable result type of the operation. + /// The target entity. + /// The name of the operation. + /// A task representing the result of the operation. + Task CallEntityAsync(EntityId entityId, string operationName); + + /// + /// Calls an operation on an entity and waits for it to complete. + /// + /// The target entity. + /// The name of the operation. + /// A task representing the completion of the operation on the entity. + Task CallEntityAsync(EntityId entityId, string operationName); + /// /// Calls an operation on an entity, passing an argument, and returns the result asynchronously. /// @@ -145,6 +162,24 @@ public interface IDurableOrchestrationContext /// if the context already holds some locks, but not the one for . Task CallEntityAsync(EntityId entityId, string operationName, object operationInput); + /// + /// Schedules an orchestrator function named for execution. + /// + /// The return type of the scheduled orchestrator function. + /// The name of the orchestrator function to call. + /// The JSON-serializeable input to pass to the orchestrator function. + /// A durable task that completes when the called orchestrator function completes or fails. + /// + /// The specified function does not exist, is disabled, or is not an orchestrator function. + /// + /// + /// The current thread is different than the thread which started the orchestrator execution. + /// + /// + /// The sub-orchestrator function failed with an unhandled exception. + /// + Task CallSubOrchestratorAsync(string functionName, object input); + /// /// Schedules an orchestration function named for execution. /// @@ -164,6 +199,41 @@ public interface IDurableOrchestrationContext /// Task CallSubOrchestratorAsync(string functionName, string instanceId, object input); + /// + /// Schedules an orchestrator function named for execution. + /// + /// The name of the orchestrator function to call. + /// The JSON-serializeable input to pass to the orchestrator function. + /// A durable task that completes when the called orchestrator function completes or fails. + /// + /// The specified function does not exist, is disabled, or is not an orchestrator function. + /// + /// + /// The current thread is different than the thread which started the orchestrator execution. + /// + /// + /// The sub-orchestrator function failed with an unhandled exception. + /// + Task CallSubOrchestratorAsync(string functionName, object input); + + /// + /// Schedules an orchestrator function named for execution. + /// + /// The name of the orchestrator function to call. + /// A unique ID to use for the sub-orchestration instance. + /// The JSON-serializeable input to pass to the orchestrator function. + /// A durable task that completes when the called orchestrator function completes or fails. + /// + /// The specified function does not exist, is disabled, or is not an orchestrator function. + /// + /// + /// The current thread is different than the thread which started the orchestrator execution. + /// + /// + /// The activity function failed with an unhandled exception. + /// + Task CallSubOrchestratorAsync(string functionName, string instanceId, object input); + /// /// Schedules an orchestrator function named for execution with retry options. /// @@ -187,6 +257,71 @@ public interface IDurableOrchestrationContext /// Task CallSubOrchestratorWithRetryAsync(string functionName, RetryOptions retryOptions, string instanceId, object input); + /// + /// Schedules an orchestrator function named for execution with retry options. + /// + /// The name of the orchestrator function to call. + /// The retry option for the orchestrator function. + /// The JSON-serializeable input to pass to the orchestrator function. + /// A durable task that completes when the called orchestrator function completes or fails. + /// + /// The retry option object is null. + /// + /// + /// The specified function does not exist, is disabled, or is not an orchestrator function. + /// + /// + /// The current thread is different than the thread which started the orchestrator execution. + /// + /// + /// The activity function failed with an unhandled exception. + /// + Task CallSubOrchestratorWithRetryAsync(string functionName, RetryOptions retryOptions, object input); + + /// + /// Schedules an orchestrator function named for execution with retry options. + /// + /// The name of the orchestrator function to call. + /// The retry option for the orchestrator function. + /// A unique ID to use for the sub-orchestration instance. + /// The JSON-serializeable input to pass to the orchestrator function. + /// A durable task that completes when the called orchestrator function completes or fails. + /// + /// The retry option object is null. + /// + /// + /// The specified function does not exist, is disabled, or is not an orchestrator function. + /// + /// + /// The current thread is different than the thread which started the orchestrator execution. + /// + /// + /// The activity function failed with an unhandled exception. + /// + Task CallSubOrchestratorWithRetryAsync(string functionName, RetryOptions retryOptions, string instanceId, object input); + + /// + /// Schedules an orchestrator function named for execution with retry options. + /// + /// The return type of the scheduled orchestrator function. + /// The name of the orchestrator function to call. + /// The retry option for the orchestrator function. + /// The JSON-serializeable input to pass to the orchestrator function. + /// A durable task that completes when the called orchestrator function completes or fails. + /// + /// The retry option object is null. + /// + /// + /// The specified function does not exist, is disabled, or is not an orchestrator function. + /// + /// + /// The current thread is different than the thread which started the orchestrator execution. + /// + /// + /// The activity function failed with an unhandled exception. + /// + Task CallSubOrchestratorWithRetryAsync(string functionName, RetryOptions retryOptions, object input); + /// /// Creates a durable timer that expires at a specified time. /// @@ -202,6 +337,19 @@ public interface IDurableOrchestrationContext /// A durable task that completes when the durable timer expires. Task CreateTimer(DateTime fireAt, T state, CancellationToken cancelToken); + /// + /// Creates a durable timer that expires at a specified time. + /// + /// + /// All durable timers created using this method must either expire or be cancelled + /// using the before the orchestrator function completes. + /// Otherwise the underlying framework will keep the instance alive until the timer expires. + /// + /// The time at which the timer should expire. + /// The CancellationToken to use for cancelling the timer. + /// A durable task that completes when the durable timer expires. + Task CreateTimer(DateTime fireAt, CancellationToken cancelToken); + /// /// Waits asynchronously for an event to be raised with name and returns the event data. /// @@ -215,21 +363,46 @@ public interface IDurableOrchestrationContext Task WaitForExternalEvent(string name); /// - /// Waits asynchronously for an event to be raised with name and returns the event data. + /// Waits asynchronously for an event to be raised with name . /// /// /// External clients can raise events to a waiting orchestration instance using - /// . + /// with the object parameter set to null. + /// + /// The name of the event to wait for. + /// A durable task that completes when the external event is received. + Task WaitForExternalEvent(string name); + + /// + /// Waits asynchronously for an event to be raised with name . + /// + /// + /// External clients can raise events to a waiting orchestration instance using + /// with the object parameter set to null. /// /// The name of the event to wait for. /// The duration after which to throw a TimeoutException. /// The CancellationToken to use for cancelling 's internal timer. - /// Any serializeable type that represents the JSON event payload. /// A durable task that completes when the external event is received. /// /// The external event was not received before the timeout expired. /// - Task WaitForExternalEvent(string name, TimeSpan timeout, CancellationToken cancelToken); + Task WaitForExternalEvent(string name, TimeSpan timeout, CancellationToken cancelToken = default(CancellationToken)); + + /// + /// Waits asynchronously for an event to be raised with name and returns the event data. + /// + /// + /// External clients can raise events to a waiting orchestration instance using + /// . + /// + /// The name of the event to wait for. + /// The duration of time to wait for the event. + /// The CancellationToken to use for cancelling 's internal timer. + /// Any serializeable type that represents the JSON event payload. + /// A durable task that completes when the external event is received, or throws a timeout exception"/> + /// if the timeout expires. + Task WaitForExternalEvent(string name, TimeSpan timeout, CancellationToken cancelToken = default(CancellationToken)); /// /// Waits asynchronously for an event to be raised with name and returns the event data. @@ -239,13 +412,14 @@ public interface IDurableOrchestrationContext /// . /// /// The name of the event to wait for. - /// The duration after which to return the value in the parameter. - /// The default value to return if the timeout expires before the external event is received. + /// The duration of time to wait for the event. + /// If specified, the default value to return if the timeout expires before the external event is received. + /// Otherwise, a timeout exception will be thrown instead. /// The CancellationToken to use for cancelling 's internal timer. /// Any serializeable type that represents the JSON event payload. /// A durable task that completes when the external event is received, or returns the value of /// if the timeout expires. - Task WaitForExternalEvent(string name, TimeSpan timeout, T defaultValue, CancellationToken cancelToken); + Task WaitForExternalEvent(string name, TimeSpan timeout, T defaultValue, CancellationToken cancelToken = default(CancellationToken)); /// /// Acquires one or more locks, for the specified entities. @@ -298,6 +472,23 @@ public interface IDurableOrchestrationContext /// Task CallActivityAsync(string functionName, object input); + /// + /// Schedules an activity function named for execution. + /// + /// The name of the activity function to call. + /// The JSON-serializeable input to pass to the activity function. + /// A durable task that completes when the called function completes or fails. + /// + /// The specified function does not exist, is disabled, or is not an orchestrator function. + /// + /// + /// The current thread is different than the thread which started the orchestrator execution. + /// + /// + /// The activity function failed with an unhandled exception. + /// + Task CallActivityAsync(string functionName, object input); + /// /// Schedules an activity function named for execution with retry options. /// @@ -320,6 +511,27 @@ public interface IDurableOrchestrationContext /// Task CallActivityWithRetryAsync(string functionName, RetryOptions retryOptions, object input); + /// + /// Schedules an activity function named for execution with retry options. + /// + /// The name of the activity function to call. + /// The retry option for the activity function. + /// The JSON-serializeable input to pass to the activity function. + /// A durable task that completes when the called activity function completes or fails. + /// + /// The retry option object is null. + /// + /// + /// The specified function does not exist, is disabled, or is not an orchestrator function. + /// + /// + /// The current thread is different than the thread which started the orchestrator execution. + /// + /// + /// The activity function failed with an unhandled exception. + /// + Task CallActivityWithRetryAsync(string functionName, RetryOptions retryOptions, object input); + /// /// Signals an entity to perform an operation, without waiting for a response. Any result or exception is ignored (fire and forget). /// @@ -349,5 +561,21 @@ public interface IDurableOrchestrationContext /// /// The instance id of the new orchestration. string StartNewOrchestration(string functionName, object input, string instanceId = null); + + /// + /// Create an entity proxy. + /// + /// The target entity key. + /// Entity interface. + /// Entity proxy. + TEntityInterface CreateEntityProxy(string entityKey); + + /// + /// Create an entity proxy. + /// + /// The target entity. + /// Entity interface. + /// Entity proxy. + TEntityInterface CreateEntityProxy(EntityId entityId); } } diff --git a/src/WebJobs.Extensions.DurableTask/DurabilityProvider.cs b/src/WebJobs.Extensions.DurableTask/DurabilityProvider.cs index b3b1780f9..60d2ef972 100644 --- a/src/WebJobs.Extensions.DurableTask/DurabilityProvider.cs +++ b/src/WebJobs.Extensions.DurableTask/DurabilityProvider.cs @@ -62,6 +62,16 @@ public DurabilityProvider(string storageProviderName, IOrchestrationService serv /// public virtual JObject ConfigurationJson => EmptyConfig; + /// + /// Value of maximum durable timer delay. Used for long running durable timers. + /// + public virtual TimeSpan MaximumDelayTime { get; set; } + + /// + /// Interval time used for long running timers. + /// + public virtual TimeSpan LongRunningTimerIntervalLength { get; set; } + /// public int TaskOrchestrationDispatcherCount => this.GetOrchestrationService().TaskOrchestrationDispatcherCount; @@ -398,5 +408,15 @@ public virtual bool ValidateDelayTime(TimeSpan timespan, out string errorMessage errorMessage = null; return true; } + + /// + /// Returns true if the stored connection string, ConnectionName, matches the input DurabilityProvider ConnectionName. + /// + /// The DurabilityProvider used to check for matching connection string names. + /// A boolean indicating whether the connection names match. + internal virtual bool ConnectionNameMatches(DurabilityProvider durabilityProvider) + { + return this.ConnectionName.Equals(durabilityProvider.ConnectionName); + } } } diff --git a/src/WebJobs.Extensions.DurableTask/DurableHttpRequest.cs b/src/WebJobs.Extensions.DurableTask/DurableHttpRequest.cs index c7c25ec68..8effe0c53 100644 --- a/src/WebJobs.Extensions.DurableTask/DurableHttpRequest.cs +++ b/src/WebJobs.Extensions.DurableTask/DurableHttpRequest.cs @@ -4,6 +4,8 @@ using System; using System.Collections.Generic; using System.Net.Http; +using Azure.Identity; +using Microsoft.Extensions.Options; using Microsoft.Extensions.Primitives; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -143,7 +145,15 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist if (Enum.TryParse((string)kindValue, out TokenSourceType tokenSourceKind) && tokenSourceKind == TokenSourceType.AzureManagedIdentity) { - return new ManagedIdentityTokenSource((string)jsonObject.GetValue("resource", StringComparison.Ordinal)); + string resourceString = (string)jsonObject.GetValue("resource", StringComparison.Ordinal); + + if (jsonObject.TryGetValue("options", out JToken optionsToken)) + { + ManagedIdentityOptions managedIdentityOptions = optionsToken.ToObject().ToObject(); + return new ManagedIdentityTokenSource(resourceString, managedIdentityOptions); + } + + return new ManagedIdentityTokenSource(resourceString); } throw new NotSupportedException($"The token source kind '{kindValue.ToString(Formatting.None)}' is not supported."); @@ -169,6 +179,13 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s writer.WriteValue(TokenSourceType.AzureManagedIdentity.ToString()); writer.WritePropertyName("resource"); writer.WriteValue(tokenSource.Resource); + + if (tokenSource.Options != null) + { + writer.WritePropertyName("options"); + writer.WriteRawValue(JsonConvert.SerializeObject(tokenSource.Options)); + } + writer.WriteEndObject(); } else diff --git a/src/WebJobs.Extensions.DurableTask/DurableTaskExtension.cs b/src/WebJobs.Extensions.DurableTask/DurableTaskExtension.cs index a600bcde4..190da9bca 100644 --- a/src/WebJobs.Extensions.DurableTask/DurableTaskExtension.cs +++ b/src/WebJobs.Extensions.DurableTask/DurableTaskExtension.cs @@ -66,6 +66,7 @@ public class DurableTaskExtension : private IDurabilityProviderFactory durabilityProviderFactory; private INameResolver nameResolver; + private ILoggerFactory loggerFactory; private DurabilityProvider defaultDurabilityProvider; private TaskHubWorker taskHubWorker; private bool isTaskHubWorkerStarted; @@ -112,13 +113,9 @@ public DurableTaskExtension( // Options will be null in Functions v1 runtime - populated later. this.Options = options?.Value ?? new DurableTaskOptions(); this.nameResolver = nameResolver ?? throw new ArgumentNullException(nameof(nameResolver)); + this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); this.ResolveAppSettingOptions(); - if (loggerFactory == null) - { - throw new ArgumentNullException(nameof(loggerFactory)); - } - ILogger logger = loggerFactory.CreateLogger(LoggerCategoryName); this.TraceHelper = new EndToEndTraceHelper(logger, this.Options.Tracing.TraceReplayEvents); @@ -290,7 +287,11 @@ void IExtensionConfigProvider.Initialize(ExtensionConfigContext context) context.AddBindingRule() .BindToTrigger(new EntityTriggerAttributeBindingProvider(this, context, storageConnectionString, this.TraceHelper)); - this.taskHubWorker = new TaskHubWorker(this.defaultDurabilityProvider, this, this); + this.taskHubWorker = new TaskHubWorker(this.defaultDurabilityProvider, this, this, this.loggerFactory); + + // Add middleware to the DTFx dispatcher so that we can inject our own logic + // into and customize the orchestration execution pipeline. + this.taskHubWorker.AddActivityDispatcherMiddleware(this.ActivityMiddleware); this.taskHubWorker.AddOrchestrationDispatcherMiddleware(this.EntityMiddleware); this.taskHubWorker.AddOrchestrationDispatcherMiddleware(this.OrchestrationMiddleware); @@ -355,11 +356,16 @@ private void InitializeForFunctionsV1(ExtensionConfigContext context) #if FUNCTIONS_V1 context.ApplyConfig(this.Options, "DurableTask"); this.nameResolver = context.Config.NameResolver; + this.loggerFactory = context.Config.LoggerFactory; this.ResolveAppSettingOptions(); - ILogger logger = context.Config.LoggerFactory.CreateLogger(LoggerCategoryName); + ILogger logger = this.loggerFactory.CreateLogger(LoggerCategoryName); this.TraceHelper = new EndToEndTraceHelper(logger, this.Options.Tracing.TraceReplayEvents); this.connectionStringResolver = new WebJobsConnectionStringProvider(); - this.durabilityProviderFactory = new AzureStorageDurabilityProviderFactory(new OptionsWrapper(this.Options), this.connectionStringResolver, this.nameResolver); + this.durabilityProviderFactory = new AzureStorageDurabilityProviderFactory( + new OptionsWrapper(this.Options), + this.connectionStringResolver, + this.nameResolver, + this.loggerFactory); this.defaultDurabilityProvider = this.durabilityProviderFactory.GetDurabilityProvider(); this.LifeCycleNotificationHelper = this.CreateLifeCycleNotificationHelper(); var messageSerializerSettingsFactory = new MessageSerializerSettingsFactory(); @@ -465,6 +471,30 @@ TaskActivity INameVersionObjectManager.GetObject(string name, stri return new TaskActivityShim(this, info.Executor, this.hostLifetimeService, name); } + /// + /// This DTFx activity middleware allows us to add context to the activity function shim + /// before it actually starts running. + /// + /// A property bag containing useful DTFx context. + /// The handler for running the next middleware in the pipeline. + private Task ActivityMiddleware(DispatchMiddlewareContext dispatchContext, Func next) + { + if (dispatchContext.GetProperty() is TaskActivityShim shim) + { + TaskScheduledEvent @event = dispatchContext.GetProperty(); + shim.SetTaskEventId(@event?.EventId ?? -1); + } + + // Move to the next stage of the DTFx pipeline to trigger the activity shim. + return next(); + } + + /// + /// This DTFx orchestration middleware allows us to initialize Durable Functions-specific context + /// and make the execution happen in a way that plays nice with the Azure Functions execution pipeline. + /// + /// A property bag containing useful DTFx context. + /// The handler for running the next middleware in the pipeline. private async Task OrchestrationMiddleware(DispatchMiddlewareContext dispatchContext, Func next) { TaskOrchestrationShim shim = dispatchContext.GetProperty() as TaskOrchestrationShim; @@ -545,7 +575,7 @@ private async Task OrchestrationMiddleware(DispatchMiddlewareContext dispatchCon $"An internal error occurred while attempting to execute '{context.FunctionName}'.", result.Exception); } - if (!context.IsCompleted) + if (!context.IsCompleted && !context.IsLongRunningTimer) { this.TraceHelper.FunctionAwaited( context.HubName, @@ -566,6 +596,12 @@ private async Task OrchestrationMiddleware(DispatchMiddlewareContext dispatchCon await context.RunDeferredTasks(); } + /// + /// This DTFx orchestration middleware (for entities) allows us to add context and set state + /// to the entity shim orchestration before it starts executing the actual entity logic. + /// + /// A property bag containing useful DTFx context. + /// The handler for running the next middleware in the pipeline. private async Task EntityMiddleware(DispatchMiddlewareContext dispatchContext, Func next) { var entityShim = dispatchContext.GetProperty() as TaskEntityShim; diff --git a/src/WebJobs.Extensions.DurableTask/EndToEndTraceHelper.cs b/src/WebJobs.Extensions.DurableTask/EndToEndTraceHelper.cs index 207e02de8..ecb596e55 100644 --- a/src/WebJobs.Extensions.DurableTask/EndToEndTraceHelper.cs +++ b/src/WebJobs.Extensions.DurableTask/EndToEndTraceHelper.cs @@ -111,7 +111,7 @@ public void FunctionScheduled( reason, functionType.ToString(), ExtensionVersion, - IsReplay: isReplay); + isReplay); if (this.ShouldLogEvent(isReplay)) { @@ -128,25 +128,27 @@ public void FunctionStarting( string instanceId, string input, FunctionType functionType, - bool isReplay) + bool isReplay, + int taskEventId = -1) { EtwEventSource.Instance.FunctionStarting( hubName, LocalAppName, LocalSlotName, functionName, + taskEventId, instanceId, input, functionType.ToString(), ExtensionVersion, - IsReplay: isReplay); + isReplay); if (this.ShouldLogEvent(isReplay)) { this.logger.LogInformation( - "{instanceId}: Function '{functionName} ({functionType})' started. IsReplay: {isReplay}. Input: {input}. State: {state}. HubName: {hubName}. AppName: {appName}. SlotName: {slotName}. ExtensionVersion: {extensionVersion}. SequenceNumber: {sequenceNumber}.", + "{instanceId}: Function '{functionName} ({functionType})' started. IsReplay: {isReplay}. Input: {input}. State: {state}. HubName: {hubName}. AppName: {appName}. SlotName: {slotName}. ExtensionVersion: {extensionVersion}. SequenceNumber: {sequenceNumber}. TaskEventId: {taskEventId}", instanceId, functionName, functionType, isReplay, input, FunctionState.Started, hubName, - LocalAppName, LocalSlotName, ExtensionVersion, this.sequenceNumber++); + LocalAppName, LocalSlotName, ExtensionVersion, this.sequenceNumber++, taskEventId); } } @@ -165,7 +167,7 @@ public void FunctionAwaited( instanceId, functionType.ToString(), ExtensionVersion, - IsReplay: isReplay); + isReplay); if (this.ShouldLogEvent(isReplay)) { @@ -194,7 +196,7 @@ public void FunctionListening( reason, functionType.ToString(), ExtensionVersion, - IsReplay: isReplay); + isReplay); if (this.ShouldLogEvent(isReplay)) { @@ -212,26 +214,28 @@ public void FunctionCompleted( string output, bool continuedAsNew, FunctionType functionType, - bool isReplay) + bool isReplay, + int taskEventId = -1) { EtwEventSource.Instance.FunctionCompleted( hubName, LocalAppName, LocalSlotName, functionName, + taskEventId, instanceId, output, continuedAsNew, functionType.ToString(), ExtensionVersion, - IsReplay: isReplay); + isReplay); if (this.ShouldLogEvent(isReplay)) { this.logger.LogInformation( - "{instanceId}: Function '{functionName} ({functionType})' completed. ContinuedAsNew: {continuedAsNew}. IsReplay: {isReplay}. Output: {output}. State: {state}. HubName: {hubName}. AppName: {appName}. SlotName: {slotName}. ExtensionVersion: {extensionVersion}. SequenceNumber: {sequenceNumber}.", + "{instanceId}: Function '{functionName} ({functionType})' completed. ContinuedAsNew: {continuedAsNew}. IsReplay: {isReplay}. Output: {output}. State: {state}. HubName: {hubName}. AppName: {appName}. SlotName: {slotName}. ExtensionVersion: {extensionVersion}. SequenceNumber: {sequenceNumber}. TaskEventId: {taskEventId}", instanceId, functionName, functionType, continuedAsNew, isReplay, output, FunctionState.Completed, - hubName, LocalAppName, LocalSlotName, ExtensionVersion, this.sequenceNumber++); + hubName, LocalAppName, LocalSlotName, ExtensionVersion, this.sequenceNumber++, taskEventId); } } @@ -242,6 +246,7 @@ public void FunctionTerminated( string reason) { FunctionType functionType = FunctionType.Orchestrator; + bool isReplay = false; EtwEventSource.Instance.FunctionTerminated( hubName, @@ -252,9 +257,9 @@ public void FunctionTerminated( reason, functionType.ToString(), ExtensionVersion, - IsReplay: false); + isReplay); - if (this.ShouldLogEvent(isReplay: false)) + if (this.ShouldLogEvent(isReplay)) { this.logger.LogWarning( "{instanceId}: Function '{functionName} ({functionType})' was terminated. Reason: {reason}. State: {state}. HubName: {hubName}. AppName: {appName}. SlotName: {slotName}. ExtensionVersion: {extensionVersion}. SequenceNumber: {sequenceNumber}.", @@ -270,6 +275,7 @@ public void FunctionRewound( string reason) { FunctionType functionType = FunctionType.Orchestrator; + bool isReplay = false; EtwEventSource.Instance.FunctionRewound( hubName, @@ -280,9 +286,9 @@ public void FunctionRewound( reason, functionType.ToString(), ExtensionVersion, - IsReplay: false); + isReplay); - if (this.ShouldLogEvent(isReplay: false)) + if (this.ShouldLogEvent(isReplay)) { this.logger.LogWarning( "{instanceId}: Function '{functionName} ({functionType})' was rewound. Reason: {reason}. State: {state}. HubName: {hubName}. AppName: {appName}. SlotName: {slotName}. ExtensionVersion: {extensionVersion}. SequenceNumber: {sequenceNumber}.", @@ -297,16 +303,27 @@ public void FunctionFailed( string instanceId, string reason, FunctionType functionType, - bool isReplay) + bool isReplay, + int taskEventId = -1) { - EtwEventSource.Instance.FunctionFailed(hubName, LocalAppName, LocalSlotName, functionName, - instanceId, reason, functionType.ToString(), ExtensionVersion, isReplay); + EtwEventSource.Instance.FunctionFailed( + hubName, + LocalAppName, + LocalSlotName, + functionName, + taskEventId, + instanceId, + reason, + functionType.ToString(), + ExtensionVersion, + isReplay); + if (this.ShouldLogEvent(isReplay)) { this.logger.LogError( - "{instanceId}: Function '{functionName} ({functionType})' failed with an error. Reason: {reason}. IsReplay: {isReplay}. State: {state}. HubName: {hubName}. AppName: {appName}. SlotName: {slotName}. ExtensionVersion: {extensionVersion}. SequenceNumber: {sequenceNumber}.", + "{instanceId}: Function '{functionName} ({functionType})' failed with an error. Reason: {reason}. IsReplay: {isReplay}. State: {state}. HubName: {hubName}. AppName: {appName}. SlotName: {slotName}. ExtensionVersion: {extensionVersion}. SequenceNumber: {sequenceNumber}. TaskEventId: {taskEventId}", instanceId, functionName, functionType, reason, isReplay, FunctionState.Failed, hubName, - LocalAppName, LocalSlotName, ExtensionVersion, this.sequenceNumber++); + LocalAppName, LocalSlotName, ExtensionVersion, this.sequenceNumber++, taskEventId); } } @@ -424,9 +441,9 @@ public void ExternalEventRaised( input, functionType.ToString(), ExtensionVersion, - IsReplay: isReplay); + isReplay); - if (this.ShouldLogEvent(isReplay: false)) + if (this.ShouldLogEvent(isReplay)) { this.logger.LogInformation( "{instanceId}: Function '{functionName} ({functionType})' received a '{eventName}' event. State: {state}. HubName: {hubName}. AppName: {appName}. SlotName: {slotName}. ExtensionVersion: {extensionVersion}. SequenceNumber: {sequenceNumber}.", @@ -452,9 +469,9 @@ public void ExternalEventSaved( eventName, functionType.ToString(), ExtensionVersion, - IsReplay: isReplay); + isReplay); - if (this.ShouldLogEvent(isReplay: false)) + if (this.ShouldLogEvent(isReplay)) { this.logger.LogInformation( "{instanceId}: Function '{functionName} ({functionType})' saved a '{eventName}' event to an in-memory queue. State: {state}. HubName: {hubName}. AppName: {appName}. SlotName: {slotName}. ExtensionVersion: {extensionVersion}. SequenceNumber: {sequenceNumber}.", @@ -509,9 +526,9 @@ public void EntityOperationQueued( operationName, functionType.ToString(), ExtensionVersion, - IsReplay: isReplay); + isReplay); - if (this.ShouldLogEvent(isReplay: isReplay)) + if (this.ShouldLogEvent(isReplay)) { this.logger.LogInformation( "{instanceId}: Function '{functionName} ({functionType})' queued '{operationName}' operation {operationId}. State: {state}. HubName: {hubName}. AppName: {appName}. SlotName: {slotName}. ExtensionVersion: {extensionVersion}. SequenceNumber: {sequenceNumber}.", @@ -539,9 +556,9 @@ public void EntityResponseReceived( result, functionType.ToString(), ExtensionVersion, - IsReplay: isReplay); + isReplay); - if (this.ShouldLogEvent(isReplay: isReplay)) + if (this.ShouldLogEvent(isReplay)) { this.logger.LogInformation( "{instanceId}: Function '{functionName} ({functionType})' received an entity response. OperationId: {operationId}. State: {state}. HubName: {hubName}. AppName: {appName}. SlotName: {slotName}. ExtensionVersion: {extensionVersion}. SequenceNumber: {sequenceNumber}.", @@ -572,9 +589,9 @@ public void EntityLockAcquired( requestId, FunctionType.Entity.ToString(), ExtensionVersion, - IsReplay: isReplay); + isReplay); - if (this.ShouldLogEvent(isReplay: isReplay)) + if (this.ShouldLogEvent(isReplay)) { this.logger.LogInformation( "{instanceId}: Function '{functionName} ({functionType})' granted lock to request {requestId} by instance {requestingInstanceId}, execution {requestingExecutionId}. State: {state}. HubName: {hubName}. AppName: {appName}. SlotName: {slotName}. ExtensionVersion: {extensionVersion}. SequenceNumber: {sequenceNumber}.", @@ -603,9 +620,9 @@ public void EntityLockReleased( requestId, FunctionType.Entity.ToString(), ExtensionVersion, - IsReplay: isReplay); + isReplay); - if (this.ShouldLogEvent(isReplay: isReplay)) + if (this.ShouldLogEvent(isReplay)) { this.logger.LogInformation( "{instanceId}: Function '{functionName} ({functionType})' released lock held by request {requestId} by instance {requestingInstance}. State: {state}. HubName: {hubName}. AppName: {appName}. SlotName: {slotName}. ExtensionVersion: {extensionVersion}. SequenceNumber: {sequenceNumber}.", @@ -642,7 +659,7 @@ public void EventGridSuccess( isReplay, latencyMs); - if (this.ShouldLogEvent(isReplay: false)) + if (this.ShouldLogEvent(isReplay)) { this.logger.LogInformation( "{instanceId}: Function '{functionName} ({functionType})' sent a '{functionState}' notification event to Azure Event Grid. Status code: {statusCode}. Details: {details}. HubName: {hubName}. AppName: {appName}. SlotName: {slotName}. ExtensionVersion: {extensionVersion}. SequenceNumber: {sequenceNumber}. Latency: {latencyMs} ms.", @@ -679,7 +696,7 @@ public void EventGridFailed( isReplay, latencyMs); - if (this.ShouldLogEvent(isReplay: false)) + if (this.ShouldLogEvent(isReplay)) { this.logger.LogError( "{instanceId}: Function '{functionName} ({functionType})' failed to send a '{functionState}' notification event to Azure Event Grid. Status code: {statusCode}. Details: {details}. HubName: {hubName}. AppName: {appName}. SlotName: {slotName}. ExtensionVersion: {extensionVersion}. SequenceNumber: {sequenceNumber}. Latency: {latencyMs} ms.", @@ -742,8 +759,8 @@ public void TimerExpired( expirationTimeString, functionType.ToString(), ExtensionVersion, - IsReplay: isReplay); - if (this.ShouldLogEvent(isReplay: false)) + isReplay); + if (this.ShouldLogEvent(isReplay)) { this.logger.LogInformation( "{instanceId}: Function '{functionName} ({functionType})' was resumed by a timer scheduled for '{expirationTime}'. IsReplay: {isReplay}. State: {state}. HubName: {hubName}. AppName: {appName}. SlotName: {slotName}. ExtensionVersion: {extensionVersion}. SequenceNumber: {sequenceNumber}.", diff --git a/src/WebJobs.Extensions.DurableTask/EntityScheduler/Proxy/DurableEntityProxyExtensions.cs b/src/WebJobs.Extensions.DurableTask/EntityScheduler/Proxy/DurableEntityProxyExtensions.cs deleted file mode 100644 index 8d37e8311..000000000 --- a/src/WebJobs.Extensions.DurableTask/EntityScheduler/Proxy/DurableEntityProxyExtensions.cs +++ /dev/null @@ -1,193 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. See LICENSE in the project root for license information. - -using System; -using System.Collections.Concurrent; -using System.Linq; -using System.Threading.Tasks; - -namespace Microsoft.Azure.WebJobs.Extensions.DurableTask -{ - /// - /// Defines convenient overloads for creating entity proxy, for all the contexts. - /// - public static class DurableEntityProxyExtensions - { - private static readonly ConcurrentDictionary EntityNameMappings = new ConcurrentDictionary(); - - /// - /// Signals an entity to perform an operation. - /// - /// Entity interface. - /// orchestration client. - /// The target entity key. - /// A delegate that performs the desired operation on the entity. - /// A task that completes when the message has been reliably enqueued. - public static Task SignalEntityAsync(this IDurableEntityClient client, string entityKey, Action operation) - { - return SignalEntityAsync(client, new EntityId(ResolveEntityName(), entityKey), operation); - } - - /// - /// Signals an entity to perform an operation, at a specified time. - /// - /// Entity interface. - /// orchestration client. - /// The target entity key. - /// The time at which to start the operation. - /// A delegate that performs the desired operation on the entity. - /// A task that completes when the message has been reliably enqueued. - public static Task SignalEntityAsync(this IDurableEntityClient client, string entityKey, DateTime scheduledTimeUtc, Action operation) - { - return SignalEntityAsync(client, new EntityId(ResolveEntityName(), entityKey), scheduledTimeUtc, operation); - } - - /// - /// Signals an entity to perform an operation. - /// - /// Entity interface. - /// orchestration client. - /// The target entity. - /// A delegate that performs the desired operation on the entity. - /// A task that completes when the message has been reliably enqueued. - public static Task SignalEntityAsync(this IDurableEntityClient client, EntityId entityId, Action operation) - { - var proxyContext = new EntityClientProxy(client); - var proxy = EntityProxyFactory.Create(proxyContext, entityId); - - operation(proxy); - - if (proxyContext.SignalTask == null) - { - throw new InvalidOperationException("The operation action must perform an operation on the entity"); - } - - return proxyContext.SignalTask; - } - - /// - /// Signals an entity to perform an operation, at a specified time. - /// - /// Entity interface. - /// orchestration client. - /// The target entity. - /// The time at which to start the operation. - /// A delegate that performs the desired operation on the entity. - /// A task that completes when the message has been reliably enqueued. - public static Task SignalEntityAsync(this IDurableEntityClient client, EntityId entityId, DateTime scheduledTimeUtc, Action operation) - { - var proxyContext = new EntityClientProxy(client, scheduledTimeUtc); - var proxy = EntityProxyFactory.Create(proxyContext, entityId); - - operation(proxy); - - if (proxyContext.SignalTask == null) - { - throw new InvalidOperationException("The operation action must perform an operation on the entity"); - } - - return proxyContext.SignalTask; - } - - /// - /// Create an entity proxy. - /// - /// orchestration context. - /// The target entity key. - /// Entity interface. - /// Entity proxy. - public static TEntityInterface CreateEntityProxy(this IDurableOrchestrationContext context, string entityKey) - { - return CreateEntityProxy(context, new EntityId(ResolveEntityName(), entityKey)); - } - - /// - /// Create an entity proxy. - /// - /// orchestration context. - /// The target entity. - /// Entity interface. - /// Entity proxy. - public static TEntityInterface CreateEntityProxy(this IDurableOrchestrationContext context, EntityId entityId) - { - return EntityProxyFactory.Create(new OrchestrationContextProxy(context), entityId); - } - - /// - /// Signals an entity to perform an operation. - /// - /// entity context. - /// The target entity key. - /// A delegate that performs the desired operation on the entity. - /// Entity interface. - public static void SignalEntity(this IDurableEntityContext context, string entityKey, Action operation) - { - SignalEntity(context, new EntityId(ResolveEntityName(), entityKey), operation); - } - - /// - /// Signals an entity to perform an operation, at a specified time. - /// - /// entity context. - /// The target entity key. - /// The time at which to start the operation. - /// A delegate that performs the desired operation on the entity. - /// Entity interface. - public static void SignalEntity(this IDurableEntityContext context, string entityKey, DateTime scheduledTimeUtc, Action operation) - { - SignalEntity(context, new EntityId(ResolveEntityName(), entityKey), scheduledTimeUtc, operation); - } - - /// - /// Signals an entity to perform an operation. - /// - /// entity context. - /// The target entity. - /// A delegate that performs the desired operation on the entity. - /// Entity interface. - public static void SignalEntity(this IDurableEntityContext context, EntityId entityId, Action operation) - { - operation(EntityProxyFactory.Create(new EntityContextProxy(context), entityId)); - } - - /// - /// Signals an entity to perform an operation, at a specified time. - /// - /// entity context. - /// The target entity. - /// The time at which to start the operation. - /// A delegate that performs the desired operation on the entity. - /// Entity interface. - public static void SignalEntity(this IDurableEntityContext context, EntityId entityId, DateTime scheduledTimeUtc, Action operation) - { - operation(EntityProxyFactory.Create(new EntityContextProxy(context, scheduledTimeUtc), entityId)); - } - - private static string ResolveEntityName() - { - var type = EntityNameMappings.GetOrAdd(typeof(TEntityInterface), CreateTypeMapping); - - return type.Name; - } - - private static Type CreateTypeMapping(Type interfaceType) - { - var implementedTypes = interfaceType.Assembly - .GetTypes() - .Where(x => x.IsClass && !x.IsAbstract && interfaceType.IsAssignableFrom(x)) - .ToArray(); - - if (!implementedTypes.Any()) - { - throw new InvalidOperationException($"Could not find any types that implement {interfaceType.FullName}."); - } - - if (implementedTypes.Length > 1) - { - throw new InvalidOperationException($"Found multiple types that implement {interfaceType.FullName}. Only one type is allowed to implement an interface used for entity proxy creation."); - } - - return implementedTypes[0]; - } - } -} diff --git a/src/WebJobs.Extensions.DurableTask/EntityScheduler/Proxy/DurableEntityProxyHelpers.cs b/src/WebJobs.Extensions.DurableTask/EntityScheduler/Proxy/DurableEntityProxyHelpers.cs new file mode 100644 index 000000000..32c787866 --- /dev/null +++ b/src/WebJobs.Extensions.DurableTask/EntityScheduler/Proxy/DurableEntityProxyHelpers.cs @@ -0,0 +1,42 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Threading.Tasks; + +namespace Microsoft.Azure.WebJobs.Extensions.DurableTask +{ + internal static class DurableEntityProxyHelpers + { + private static readonly ConcurrentDictionary EntityNameMappings = new ConcurrentDictionary(); + + internal static string ResolveEntityName() + { + var type = EntityNameMappings.GetOrAdd(typeof(TEntityInterface), CreateTypeMapping); + + return type.Name; + } + + private static Type CreateTypeMapping(Type interfaceType) + { + var implementedTypes = interfaceType.Assembly + .GetTypes() + .Where(x => x.IsClass && !x.IsAbstract && interfaceType.IsAssignableFrom(x)) + .ToArray(); + + if (!implementedTypes.Any()) + { + throw new InvalidOperationException($"Could not find any types that implement {interfaceType.FullName}."); + } + + if (implementedTypes.Length > 1) + { + throw new InvalidOperationException($"Found multiple types that implement {interfaceType.FullName}. Only one type is allowed to implement an interface used for entity proxy creation."); + } + + return implementedTypes[0]; + } + } +} diff --git a/src/WebJobs.Extensions.DurableTask/EtwEventSource.cs b/src/WebJobs.Extensions.DurableTask/EtwEventSource.cs index 40ab4a6f1..3aa28b0e0 100644 --- a/src/WebJobs.Extensions.DurableTask/EtwEventSource.cs +++ b/src/WebJobs.Extensions.DurableTask/EtwEventSource.cs @@ -34,19 +34,20 @@ public void FunctionScheduled( this.WriteEvent(201, TaskHub, AppName, SlotName, FunctionName, InstanceId, Reason, FunctionType, ExtensionVersion, IsReplay); } - [Event(202, Level = EventLevel.Informational, Version = 4)] + [Event(202, Level = EventLevel.Informational, Version = 5)] public void FunctionStarting( string TaskHub, string AppName, string SlotName, string FunctionName, + int TaskEventId, string InstanceId, string Input, string FunctionType, string ExtensionVersion, bool IsReplay) { - this.WriteEvent(202, TaskHub, AppName, SlotName, FunctionName, InstanceId, Input ?? "(null)", FunctionType, ExtensionVersion, IsReplay); + this.WriteEvent(202, TaskHub, AppName, SlotName, FunctionName, TaskEventId, InstanceId, Input ?? "(null)", FunctionType, ExtensionVersion, IsReplay); } [Event(203, Level = EventLevel.Informational, Version = 4)] @@ -94,12 +95,13 @@ public void ExternalEventRaised( this.WriteEvent(205, TaskHub, AppName, SlotName, FunctionName, InstanceId, EventName, Input ?? "(null)", FunctionType, ExtensionVersion, IsReplay); } - [Event(206, Level = EventLevel.Informational, Version = 4)] + [Event(206, Level = EventLevel.Informational, Version = 5)] public void FunctionCompleted( string TaskHub, string AppName, string SlotName, string FunctionName, + int TaskEventId, string InstanceId, string Output, bool ContinuedAsNew, @@ -107,7 +109,7 @@ public void FunctionCompleted( string ExtensionVersion, bool IsReplay) { - this.WriteEvent(206, TaskHub, AppName, SlotName, FunctionName, InstanceId, Output ?? "(null)", ContinuedAsNew, FunctionType, ExtensionVersion, IsReplay); + this.WriteEvent(206, TaskHub, AppName, SlotName, FunctionName, TaskEventId, InstanceId, Output ?? "(null)", ContinuedAsNew, FunctionType, ExtensionVersion, IsReplay); } [Event(207, Level = EventLevel.Warning, Version = 2)] @@ -125,19 +127,20 @@ public void FunctionTerminated( this.WriteEvent(207, TaskHub, AppName, SlotName, FunctionName, InstanceId, Reason, FunctionType, ExtensionVersion, IsReplay); } - [Event(208, Level = EventLevel.Error, Version = 3)] + [Event(208, Level = EventLevel.Error, Version = 4)] public void FunctionFailed( string TaskHub, string AppName, string SlotName, string FunctionName, + int TaskEventId, string InstanceId, string Reason, string FunctionType, string ExtensionVersion, bool IsReplay) { - this.WriteEvent(208, TaskHub, AppName, SlotName, FunctionName, InstanceId, Reason, FunctionType, ExtensionVersion, IsReplay); + this.WriteEvent(208, TaskHub, AppName, SlotName, FunctionName, TaskEventId, InstanceId, Reason, FunctionType, ExtensionVersion, IsReplay); } [Event(209, Level = EventLevel.Informational, Version = 2)] diff --git a/src/WebJobs.Extensions.DurableTask/HttpApiHandler.cs b/src/WebJobs.Extensions.DurableTask/HttpApiHandler.cs index c65e21894..5c3894eea 100644 --- a/src/WebJobs.Extensions.DurableTask/HttpApiHandler.cs +++ b/src/WebJobs.Extensions.DurableTask/HttpApiHandler.cs @@ -777,7 +777,7 @@ private async Task HandleRaiseEventRequestAsync( object eventData; try { - eventData = !string.IsNullOrEmpty(stringData) ? JToken.Parse(stringData) : null; + eventData = MessagePayloadDataConverter.ConvertToJToken(stringData); } catch (JsonReaderException e) { @@ -843,7 +843,7 @@ private async Task HandlePostEntityOperationRequestAsync( { try { - operationInput = JToken.Parse(requestContent); + operationInput = MessagePayloadDataConverter.ConvertToJToken(requestContent); } catch (JsonException e) { diff --git a/src/WebJobs.Extensions.DurableTask/Listener/OutOfProcOrchestrationShim.cs b/src/WebJobs.Extensions.DurableTask/Listener/OutOfProcOrchestrationShim.cs index 0dfa592d7..2004c9565 100644 --- a/src/WebJobs.Extensions.DurableTask/Listener/OutOfProcOrchestrationShim.cs +++ b/src/WebJobs.Extensions.DurableTask/Listener/OutOfProcOrchestrationShim.cs @@ -127,7 +127,17 @@ private async Task ProcessAsyncActions(AsyncAction[][] actions) case AsyncActionType.CreateTimer: using (var cts = new CancellationTokenSource()) { - tasks.Add(this.context.CreateTimer(action.FireAt, cts.Token)); + DurableOrchestrationContext ctx = this.context as DurableOrchestrationContext; + + if (ctx != null) + { + tasks.Add(ctx.OutOfProcCreateTimer(ctx, action.FireAt, cts.Token)); + } + else + { + tasks.Add(this.context.CreateTimer(action.FireAt, cts.Token)); + } + if (action.IsCanceled) { cts.Cancel(); diff --git a/src/WebJobs.Extensions.DurableTask/Listener/TaskActivityShim.cs b/src/WebJobs.Extensions.DurableTask/Listener/TaskActivityShim.cs index 96db8dbb1..3f20fdeb5 100644 --- a/src/WebJobs.Extensions.DurableTask/Listener/TaskActivityShim.cs +++ b/src/WebJobs.Extensions.DurableTask/Listener/TaskActivityShim.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. See LICENSE in the project root for license information. using System; -using System.Threading; using System.Threading.Tasks; using DurableTask.Core; using DurableTask.Core.Common; @@ -23,6 +22,11 @@ internal class TaskActivityShim : TaskActivity private readonly IApplicationLifetimeWrapper hostServiceLifetime; private readonly string activityName; + /// + /// The DTFx-generated, auto-incrementing ID that uniquely identifies this activity function execution. + /// + private int taskEventId = -1; + public TaskActivityShim( DurableTaskExtension config, ITriggeredFunctionExecutor executor, @@ -56,7 +60,8 @@ public override async Task RunAsync(TaskContext context, string rawInput instanceId, this.config.GetIntputOutputTrace(rawInput), functionType: FunctionType.Activity, - isReplay: false); + isReplay: false, + taskEventId: this.taskEventId); WrappedFunctionResult result = await FunctionExecutionHelper.ExecuteActivityFunction( this.executor, @@ -74,7 +79,8 @@ public override async Task RunAsync(TaskContext context, string rawInput this.config.GetIntputOutputTrace(serializedOutput), continuedAsNew: false, functionType: FunctionType.Activity, - isReplay: false); + isReplay: false, + taskEventId: this.taskEventId); return serializedOutput; case WrappedFunctionResult.FunctionResultStatus.FunctionsRuntimeError: @@ -106,7 +112,8 @@ public override async Task RunAsync(TaskContext context, string rawInput instanceId, exceptionToReport?.ToString() ?? string.Empty, functionType: FunctionType.Activity, - isReplay: false); + isReplay: false, + taskEventId: this.taskEventId); throw new TaskFailureException( $"Activity function '{this.activityName}' failed: {exceptionToReport.Message}", @@ -122,6 +129,13 @@ public override string Run(TaskContext context, string input) throw new NotImplementedException(); } + internal void SetTaskEventId(int taskEventId) + { + // We don't have the DTFx task event ID at TaskActivityShim-creation time + // so we have to set it here, before DTFx calls the RunAsync method. + this.taskEventId = taskEventId; + } + private static Exception StripFunctionInvocationException(Exception e) { var infrastructureException = e as FunctionInvocationException; diff --git a/src/WebJobs.Extensions.DurableTask/ManagedIdentityOptions.cs b/src/WebJobs.Extensions.DurableTask/ManagedIdentityOptions.cs new file mode 100644 index 000000000..bd1ea866f --- /dev/null +++ b/src/WebJobs.Extensions.DurableTask/ManagedIdentityOptions.cs @@ -0,0 +1,37 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using System; +using Newtonsoft.Json; + +namespace Microsoft.Azure.WebJobs.Extensions.DurableTask +{ + /// + /// Configuration options for Managed Identity. + /// + public class ManagedIdentityOptions + { + /// + /// Initializes a new instance of the class. + /// + /// The host of the Azure Active Directory authority. + /// The tenant id of the user to authenticate. + public ManagedIdentityOptions(Uri authorityHost = null, string tenantId = null) + { + this.AuthorityHost = authorityHost; + this.TenantId = tenantId; + } + + /// + /// The host of the Azure Active Directory authority. The default is https://login.microsoftonline.com/. + /// + [JsonProperty("authorityhost")] + public Uri AuthorityHost { get; set; } + + /// + /// The tenant id of the user to authenticate. + /// + [JsonProperty("tenantid")] + public string TenantId { get; set; } + } +} diff --git a/src/WebJobs.Extensions.DurableTask/ManagedIdentityTokenSource.cs b/src/WebJobs.Extensions.DurableTask/ManagedIdentityTokenSource.cs index cf5779d1d..c28b7f9d3 100644 --- a/src/WebJobs.Extensions.DurableTask/ManagedIdentityTokenSource.cs +++ b/src/WebJobs.Extensions.DurableTask/ManagedIdentityTokenSource.cs @@ -3,7 +3,8 @@ using System; using System.Threading.Tasks; -using Microsoft.Azure.Services.AppAuthentication; +using Azure.Core; +using Azure.Identity; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; @@ -21,9 +22,11 @@ public class ManagedIdentityTokenSource : ITokenSource /// The Azure Active Directory resource identifier of the web API being invoked. /// For example, https://management.core.windows.net/ or https://graph.microsoft.com/. /// - public ManagedIdentityTokenSource(string resource) + /// Optional Azure credential options to use when authenticating. + public ManagedIdentityTokenSource(string resource, ManagedIdentityOptions options = null) { this.Resource = resource ?? throw new ArgumentNullException(nameof(resource)); + this.Options = options; } /// @@ -33,11 +36,36 @@ public ManagedIdentityTokenSource(string resource) [JsonProperty("resource")] public string Resource { get; } + /// + /// The azure credential options that a user can configure when authenticating. + /// + [JsonProperty("options")] + public ManagedIdentityOptions Options { get; } + /// public async Task GetTokenAsync() { - var azureServiceTokenProvider = new AzureServiceTokenProvider(); - string accessToken = await azureServiceTokenProvider.GetAccessTokenAsync(this.Resource); + var scopes = new string[] { this.Resource }; + TokenRequestContext context = new TokenRequestContext(scopes); + + DefaultAzureCredential defaultCredential; + DefaultAzureCredentialOptions defaultAzureCredentialOptions = new DefaultAzureCredentialOptions(); + + if (this.Options?.AuthorityHost != null) + { + defaultAzureCredentialOptions.AuthorityHost = this.Options.AuthorityHost; + } + + if (!string.IsNullOrEmpty(this.Options?.TenantId)) + { + defaultAzureCredentialOptions.InteractiveBrowserTenantId = this.Options.TenantId; + } + + defaultCredential = this.Options == null ? new DefaultAzureCredential() : new DefaultAzureCredential(defaultAzureCredentialOptions); + + AccessToken defaultToken = await defaultCredential.GetTokenAsync(context); + string accessToken = defaultToken.Token; + return accessToken; } } diff --git a/src/WebJobs.Extensions.DurableTask/MessagePayloadDataConverter.cs b/src/WebJobs.Extensions.DurableTask/MessagePayloadDataConverter.cs index 9438b5ef2..fd4642e14 100644 --- a/src/WebJobs.Extensions.DurableTask/MessagePayloadDataConverter.cs +++ b/src/WebJobs.Extensions.DurableTask/MessagePayloadDataConverter.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. See LICENSE in the project root for license information. using System; +using System.IO; using System.Text; using DurableTask.Core.Serializing; using Newtonsoft.Json; @@ -67,5 +68,35 @@ public string Serialize(object value, int maxSizeInKB) return serializedJson; } + + public static JToken ConvertToJToken(string input) + { + JToken token = null; + if (input != null) + { + using (var stringReader = new StringReader(input)) + using (var jsonTextReader = new JsonTextReader(stringReader) { DateParseHandling = DateParseHandling.None }) + { + return token = JToken.Load(jsonTextReader); + } + } + + return token; + } + + public static JArray ConvertToJArray(string input) + { + JArray jArray = null; + if (input != null) + { + using (var stringReader = new StringReader(input)) + using (var jsonTextReader = new JsonTextReader(stringReader) { DateParseHandling = DateParseHandling.None }) + { + jArray = JArray.Load(jsonTextReader); + } + } + + return jArray; + } } } diff --git a/src/WebJobs.Extensions.DurableTask/MessageSerializerSettingsFactory.cs b/src/WebJobs.Extensions.DurableTask/MessageSerializerSettingsFactory.cs index 3f598f6be..cbb88cf81 100644 --- a/src/WebJobs.Extensions.DurableTask/MessageSerializerSettingsFactory.cs +++ b/src/WebJobs.Extensions.DurableTask/MessageSerializerSettingsFactory.cs @@ -28,6 +28,7 @@ public JsonSerializerSettings CreateJsonSerializerSettings() this.jsonSerializerSettings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.None, + DateParseHandling = DateParseHandling.None, }; } diff --git a/src/WebJobs.Extensions.DurableTask/Microsoft.Azure.WebJobs.Extensions.DurableTask-net461.xml b/src/WebJobs.Extensions.DurableTask/Microsoft.Azure.WebJobs.Extensions.DurableTask-net461.xml index 630a38bb1..db10ce8c2 100644 --- a/src/WebJobs.Extensions.DurableTask/Microsoft.Azure.WebJobs.Extensions.DurableTask-net461.xml +++ b/src/WebJobs.Extensions.DurableTask/Microsoft.Azure.WebJobs.Extensions.DurableTask-net461.xml @@ -93,10 +93,10 @@ - + - + @@ -123,10 +123,7 @@ - - - - + @@ -141,6 +138,24 @@ + + + + + + + + + + + + + + + + + + Common functionality used by both @@ -152,6 +167,15 @@ Context object passed to application code executing entity operations. + + + + + + + + + Parameter data for orchestration bindings that can be used to schedule function-based activities. @@ -254,392 +278,52 @@ - - - Defines convenient overloads for calling the context methods, for all the contexts. - - - - - Schedules an activity function named for execution. - - The context object. - The name of the activity function to call. - The JSON-serializeable input to pass to the activity function. - A durable task that completes when the called function completes or fails. - - The specified function does not exist, is disabled, or is not an orchestrator function. - - - The current thread is different than the thread which started the orchestrator execution. - - - The activity function failed with an unhandled exception. - - - - - Schedules an activity function named for execution with retry options. - - The context object. - The name of the activity function to call. - The retry option for the activity function. - The JSON-serializeable input to pass to the activity function. - A durable task that completes when the called activity function completes or fails. - - The retry option object is null. - - - The specified function does not exist, is disabled, or is not an orchestrator function. - - - The current thread is different than the thread which started the orchestrator execution. - - - The activity function failed with an unhandled exception. - - - - - Schedules an orchestrator function named for execution. - - The context object. - The name of the orchestrator function to call. - The JSON-serializeable input to pass to the orchestrator function. - A durable task that completes when the called orchestrator function completes or fails. - - The specified function does not exist, is disabled, or is not an orchestrator function. - - - The current thread is different than the thread which started the orchestrator execution. - - - The sub-orchestrator function failed with an unhandled exception. - - - - - Schedules an orchestrator function named for execution. - - The context object. - The name of the orchestrator function to call. - A unique ID to use for the sub-orchestration instance. - The JSON-serializeable input to pass to the orchestrator function. - A durable task that completes when the called orchestrator function completes or fails. - - The specified function does not exist, is disabled, or is not an orchestrator function. - - - The current thread is different than the thread which started the orchestrator execution. - - - The activity function failed with an unhandled exception. - - - - - Schedules an orchestration function named for execution. - - The return type of the scheduled orchestrator function. - The context object. - The name of the orchestrator function to call. - The JSON-serializeable input to pass to the orchestrator function. - A durable task that completes when the called orchestrator function completes or fails. - - The specified function does not exist, is disabled, or is not an orchestrator function. - - - The current thread is different than the thread which started the orchestrator execution. - - - The activity function failed with an unhandled exception. - - - - - Schedules an orchestrator function named for execution with retry options. - - The context object. - The name of the orchestrator function to call. - The retry option for the orchestrator function. - The JSON-serializeable input to pass to the orchestrator function. - A durable task that completes when the called orchestrator function completes or fails. - - The retry option object is null. - - - The specified function does not exist, is disabled, or is not an orchestrator function. - - - The current thread is different than the thread which started the orchestrator execution. - - - The activity function failed with an unhandled exception. - - - - - Schedules an orchestrator function named for execution with retry options. - - The context object. - The name of the orchestrator function to call. - The retry option for the orchestrator function. - A unique ID to use for the sub-orchestration instance. - The JSON-serializeable input to pass to the orchestrator function. - A durable task that completes when the called orchestrator function completes or fails. - - The retry option object is null. - - - The specified function does not exist, is disabled, or is not an orchestrator function. - - - The current thread is different than the thread which started the orchestrator execution. - - - The activity function failed with an unhandled exception. - - - - - Schedules an orchestrator function named for execution with retry options. - - The return type of the scheduled orchestrator function. - The context object. - The name of the orchestrator function to call. - The retry option for the orchestrator function. - The JSON-serializeable input to pass to the orchestrator function. - A durable task that completes when the called orchestrator function completes or fails. - - The retry option object is null. - - - The specified function does not exist, is disabled, or is not an orchestrator function. - - - The current thread is different than the thread which started the orchestrator execution. - - - The activity function failed with an unhandled exception. - - - - - Creates a durable timer that expires at a specified time. - - - All durable timers created using this method must either expire or be cancelled - using the before the orchestrator function completes. - Otherwise the underlying framework will keep the instance alive until the timer expires. - - The context object. - The time at which the timer should expire. - The CancellationToken to use for cancelling the timer. - A durable task that completes when the durable timer expires. - - - - Waits asynchronously for an event to be raised with name . - - - External clients can raise events to a waiting orchestration instance using - with the object parameter set to null. - - The context object. - The name of the event to wait for. - A durable task that completes when the external event is received. - - - - Waits asynchronously for an event to be raised with name . - - - External clients can raise events to a waiting orchestration instance using - with the object parameter set to null. - - The context object. - The name of the event to wait for. - The duration after which to throw a TimeoutException. - A durable task that completes when the external event is received. - - The external event was not received before the timeout expired. - - - - - Waits asynchronously for an event to be raised with name . - - - External clients can raise events to a waiting orchestration instance using - with the object parameter set to null. - - The context object. - The name of the event to wait for. - The duration after which to throw a TimeoutException. - The CancellationToken to use for cancelling 's internal timer. - A durable task that completes when the external event is received. - - The external event was not received before the timeout expired. - - - - - Waits asynchronously for an event to be raised with name and returns the event data. - - - External clients can raise events to a waiting orchestration instance using - . - - The context object. - The name of the event to wait for. - The duration after which to throw a TimeoutException. - Any serializeable type that represents the JSON event payload. - A durable task that completes when the external event is received. - - The external event was not received before the timeout expired. - - - - - Waits asynchronously for an event to be raised with name and returns the event data. - - - External clients can raise events to a waiting orchestration instance using - . - - The context object. - The name of the event to wait for. - The duration after which to return the value in the parameter. - The default value to return if the timeout expires before the external event is received. - Any serializeable type that represents the JSON event payload. - A durable task that completes when the external event is received, or returns the value of - if the timeout expires. - - - - Calls an operation on an entity and returns the result asynchronously. - - The JSON-serializable result type of the operation. - The context object. - The target entity. - The name of the operation. - A task representing the result of the operation. + + - - - Calls an operation on an entity and waits for it to complete. - - The context object. - The target entity. - The name of the operation. - A task representing the completion of the operation on the entity. + + - - - Creates an HTTP response which either contains a payload of management URLs for a non-completed instance - or contains the payload containing the output of the completed orchestration. - - - If the orchestration instance completes within the default 10 second timeout, then the HTTP response payload will - contain the output of the orchestration instance formatted as JSON. However, if the orchestration does not - complete within this timeout, then the HTTP response will be identical to that of the - API. - - The client object. - The HTTP request that triggered the current function. - The unique ID of the instance to check. - An HTTP response which may include a 202 and location header or a 200 with the durable function output in the response body. + + - - - Creates an HTTP response which either contains a payload of management URLs for a non-completed instance - or contains the payload containing the output of the completed orchestration. - - - If the orchestration instance completes within the specified timeout, then the HTTP response payload will - contain the output of the orchestration instance formatted as JSON. However, if the orchestration does not - complete within the specified timeout, then the HTTP response will be identical to that of the - API. - - The client object. - The HTTP request that triggered the current function. - The unique ID of the instance to check. - Total allowed timeout for output from the durable function. The default value is 10 seconds. - An HTTP response which may include a 202 and location header or a 200 with the durable function output in the response body. + + - - - Starts a new execution of the specified orchestrator function. - - The client object. - The name of the orchestrator function to start. - The ID to use for the new orchestration instance. - A task that completes when the orchestration is started. The task contains the instance id of the started - orchestratation instance. - - The specified function does not exist, is disabled, or is not an orchestrator function. - + + - - - Starts a new execution of the specified orchestrator function. - - The client object. - The name of the orchestrator function to start. - JSON-serializeable input value for the orchestrator function. - The type of the input value for the orchestrator function. - A task that completes when the orchestration is started. The task contains the instance id of the started - orchestratation instance. - - The specified function does not exist, is disabled, or is not an orchestrator function. - + + - - - Starts a new execution of the specified orchestrator function. - - The client object. - The name of the orchestrator function to start. - A task that completes when the orchestration is started. The task contains the instance id of the started - orchestratation instance. - - The specified function does not exist, is disabled, or is not an orchestrator function. - + + - - - Sends an event notification message to a waiting orchestration instance. - - - - In order to handle the event, the target orchestration instance must be waiting for an - event named using the - API. - - - The instance id does not corespond to a valid orchestration instance. - The orchestration instance with the provided instance id is not running. - The client object. - The ID of the orchestration instance that will handle the event. - The name of the event. - A task that completes when the event notification message has been enqueued. + + - - - Gets the status of the specified orchestration instance. - - The client object. - The ID of the orchestration instance to query. - Returns a task which completes when the status has been fetched. + + - + + + + + + + + + + + + + + + + - Gets the status of the specified orchestration instance. + Defines convenient overloads for calling the context methods, for all the contexts. - The client object. - The ID of the orchestration instance to query. - Boolean marker for including execution history in the response. - Returns a task which completes when the status has been fetched. @@ -723,6 +407,44 @@ The name of the connection string associated with . A task that completes when the message has been reliably enqueued. + + + Signals an entity to perform an operation. + + Entity interface. + The target entity key. + A delegate that performs the desired operation on the entity. + A task that completes when the message has been reliably enqueued. + + + + Signals an entity to perform an operation, at a specified time. + + Entity interface. + The target entity key. + The time at which to start the operation. + A delegate that performs the desired operation on the entity. + A task that completes when the message has been reliably enqueued. + + + + Signals an entity to perform an operation. + + Entity interface. + The target entity. + A delegate that performs the desired operation on the entity. + A task that completes when the message has been reliably enqueued. + + + + Signals an entity to perform an operation, at a specified time. + + Entity interface. + The target entity. + The time at which to start the operation. + A delegate that performs the desired operation on the entity. + A task that completes when the message has been reliably enqueued. + Tries to read the current state of an entity. Returns default() if the entity does not @@ -842,6 +564,40 @@ The name of the operation. The input for the operation. + + + Signals an entity to perform an operation. + + The target entity key. + A delegate that performs the desired operation on the entity. + Entity interface. + + + + Signals an entity to perform an operation, at a specified time. + + The target entity key. + The time at which to start the operation. + A delegate that performs the desired operation on the entity. + Entity interface. + + + + Signals an entity to perform an operation. + + The target entity. + A delegate that performs the desired operation on the entity. + Entity interface. + + + + Signals an entity to perform an operation, at a specified time. + + The target entity. + The time at which to start the operation. + A delegate that performs the desired operation on the entity. + Entity interface. + Schedules a orchestration function named for execution./>. @@ -907,7 +663,7 @@ The ID of the orchestration instance to check. Instance of the class. - + Creates an HTTP response which either contains a payload of management URLs for a non-completed instance or contains the payload containing the output of the completed orchestration. @@ -924,7 +680,7 @@ The timeout between checks for output from the durable function. The default value is 1 second. An HTTP response which may include a 202 and location header or a 200 with the durable function output in the response body. - + Creates an HTTP response which either contains a payload of management URLs for a non-completed instance or contains the payload containing the output of the completed orchestration. @@ -941,6 +697,31 @@ The timeout between checks for output from the durable function. The default value is 1 second. An HTTP response which may include a 202 and location header or a 200 with the durable function output in the response body. + + + Starts a new execution of the specified orchestrator function. + + The name of the orchestrator function to start. + The ID to use for the new orchestration instance. + A task that completes when the orchestration is started. The task contains the instance id of the started + orchestratation instance. + + The specified function does not exist, is disabled, or is not an orchestrator function. + + + + + Starts a new execution of the specified orchestrator function. + + The name of the orchestrator function to start. + JSON-serializeable input value for the orchestrator function. + The type of the input value for the orchestrator function. + A task that completes when the orchestration is started. The task contains the instance id of the started + orchestratation instance. + + The specified function does not exist, is disabled, or is not an orchestrator function. + + Starts a new instance of the specified orchestrator function. @@ -1039,21 +820,14 @@ If set, fetch and return the input for the orchestration instance. Returns a task which completes when the status has been fetched. - - - Gets all the status of the orchestration instances. - - Cancellation token that can be used to cancel the status query operation. - Returns orchestration status for all instances. - - + Gets the status of all orchestration instances that match the specified conditions. - Return orchestration instances which were created after this DateTime. - Return orchestration instances which were created before this DateTime. - Return orchestration instances which matches the runtimeStatus. - Cancellation token that can be used to cancel the status query operation. + If specified, return orchestration instances which were created after this DateTime. + If specified, return orchestration instances which were created before this DateTime. + If specified, return orchestration instances which matches the runtimeStatus. + If specified, this ancellation token can be used to cancel the status query operation. Returns orchestration status for all instances. @@ -1203,6 +977,23 @@ The DurableHttpRequest used to make the HTTP call. A Result of the HTTP call. + + + Calls an operation on an entity and returns the result asynchronously. + + The JSON-serializable result type of the operation. + The target entity. + The name of the operation. + A task representing the result of the operation. + + + + Calls an operation on an entity and waits for it to complete. + + The target entity. + The name of the operation. + A task representing the completion of the operation on the entity. + Calls an operation on an entity, passing an argument, and returns the result asynchronously. @@ -1214,25 +1005,144 @@ A task representing the result of the operation. if the context already holds some locks, but not the one for . - + + + Calls an operation on an entity, passing an argument, and waits for it to complete. + + The target entity. + The name of the operation. + The input for the operation. + A task representing the completion of the operation on the entity. + if the context already holds some locks, but not the one for . + + + + Schedules an orchestrator function named for execution. + + The return type of the scheduled orchestrator function. + The name of the orchestrator function to call. + The JSON-serializeable input to pass to the orchestrator function. + A durable task that completes when the called orchestrator function completes or fails. + + The specified function does not exist, is disabled, or is not an orchestrator function. + + + The current thread is different than the thread which started the orchestrator execution. + + + The sub-orchestrator function failed with an unhandled exception. + + + + + Schedules an orchestration function named for execution. + + The return type of the scheduled orchestrator function. + The name of the orchestrator function to call. + A unique ID to use for the sub-orchestration instance. + The JSON-serializeable input to pass to the orchestrator function. + A durable task that completes when the called orchestrator function completes or fails. + + The specified function does not exist, is disabled, or is not an orchestrator function. + + + The current thread is different than the thread which started the orchestrator execution. + + + The activity function failed with an unhandled exception. + + + + + Schedules an orchestrator function named for execution. + + The name of the orchestrator function to call. + The JSON-serializeable input to pass to the orchestrator function. + A durable task that completes when the called orchestrator function completes or fails. + + The specified function does not exist, is disabled, or is not an orchestrator function. + + + The current thread is different than the thread which started the orchestrator execution. + + + The sub-orchestrator function failed with an unhandled exception. + + + + + Schedules an orchestrator function named for execution. + + The name of the orchestrator function to call. + A unique ID to use for the sub-orchestration instance. + The JSON-serializeable input to pass to the orchestrator function. + A durable task that completes when the called orchestrator function completes or fails. + + The specified function does not exist, is disabled, or is not an orchestrator function. + + + The current thread is different than the thread which started the orchestrator execution. + + + The activity function failed with an unhandled exception. + + + + + Schedules an orchestrator function named for execution with retry options. + + The return type of the scheduled orchestrator function. + The name of the orchestrator function to call. + The retry option for the orchestrator function. + A unique ID to use for the sub-orchestration instance. + The JSON-serializeable input to pass to the orchestrator function. + A durable task that completes when the called orchestrator function completes or fails. + + The retry option object is null. + + + The specified function does not exist, is disabled, or is not an orchestrator function. + + + The current thread is different than the thread which started the orchestrator execution. + + + The activity function failed with an unhandled exception. + + + - Calls an operation on an entity, passing an argument, and waits for it to complete. + Schedules an orchestrator function named for execution with retry options. - The target entity. - The name of the operation. - The input for the operation. - A task representing the completion of the operation on the entity. - if the context already holds some locks, but not the one for . + The name of the orchestrator function to call. + The retry option for the orchestrator function. + The JSON-serializeable input to pass to the orchestrator function. + A durable task that completes when the called orchestrator function completes or fails. + + The retry option object is null. + + + The specified function does not exist, is disabled, or is not an orchestrator function. + + + The current thread is different than the thread which started the orchestrator execution. + + + The activity function failed with an unhandled exception. + - + - Schedules an orchestration function named for execution. + Schedules an orchestrator function named for execution with retry options. - The return type of the scheduled orchestrator function. The name of the orchestrator function to call. + The retry option for the orchestrator function. A unique ID to use for the sub-orchestration instance. The JSON-serializeable input to pass to the orchestrator function. A durable task that completes when the called orchestrator function completes or fails. + + The retry option object is null. + The specified function does not exist, is disabled, or is not an orchestrator function. @@ -1243,14 +1153,13 @@ The activity function failed with an unhandled exception. - + Schedules an orchestrator function named for execution with retry options. The return type of the scheduled orchestrator function. The name of the orchestrator function to call. The retry option for the orchestrator function. - A unique ID to use for the sub-orchestration instance. The JSON-serializeable input to pass to the orchestrator function. A durable task that completes when the called orchestrator function completes or fails. @@ -1281,6 +1190,19 @@ The CancellationToken to use for cancelling the timer. A durable task that completes when the durable timer expires. + + + Creates a durable timer that expires at a specified time. + + + All durable timers created using this method must either expire or be cancelled + using the before the orchestrator function completes. + Otherwise the underlying framework will keep the instance alive until the timer expires. + + The time at which the timer should expire. + The CancellationToken to use for cancelling the timer. + A durable task that completes when the durable timer expires. + Waits asynchronously for an event to be raised with name and returns the event data. @@ -1293,23 +1215,48 @@ Any serializeable type that represents the JSON event payload. A durable task that completes when the external event is received. - + - Waits asynchronously for an event to be raised with name and returns the event data. + Waits asynchronously for an event to be raised with name . External clients can raise events to a waiting orchestration instance using - . + with the object parameter set to null. + + The name of the event to wait for. + A durable task that completes when the external event is received. + + + + Waits asynchronously for an event to be raised with name . + + + External clients can raise events to a waiting orchestration instance using + with the object parameter set to null. The name of the event to wait for. The duration after which to throw a TimeoutException. The CancellationToken to use for cancelling 's internal timer. - Any serializeable type that represents the JSON event payload. A durable task that completes when the external event is received. The external event was not received before the timeout expired. + + + Waits asynchronously for an event to be raised with name and returns the event data. + + + External clients can raise events to a waiting orchestration instance using + . + + The name of the event to wait for. + The duration of time to wait for the event. + The CancellationToken to use for cancelling 's internal timer. + Any serializeable type that represents the JSON event payload. + A durable task that completes when the external event is received, or throws a timeout exception"/> + if the timeout expires. + Waits asynchronously for an event to be raised with name and returns the event data. @@ -1319,8 +1266,9 @@ . The name of the event to wait for. - The duration after which to return the value in the parameter. - The default value to return if the timeout expires before the external event is received. + The duration of time to wait for the event. + If specified, the default value to return if the timeout expires before the external event is received. + Otherwise, a timeout exception will be thrown instead. The CancellationToken to use for cancelling 's internal timer. Any serializeable type that represents the JSON event payload. A durable task that completes when the external event is received, or returns the value of @@ -1377,6 +1325,23 @@ The activity function failed with an unhandled exception. + + + Schedules an activity function named for execution. + + The name of the activity function to call. + The JSON-serializeable input to pass to the activity function. + A durable task that completes when the called function completes or fails. + + The specified function does not exist, is disabled, or is not an orchestrator function. + + + The current thread is different than the thread which started the orchestrator execution. + + + The activity function failed with an unhandled exception. + + Schedules an activity function named for execution with retry options. @@ -1399,6 +1364,27 @@ The activity function failed with an unhandled exception. + + + Schedules an activity function named for execution with retry options. + + The name of the activity function to call. + The retry option for the activity function. + The JSON-serializeable input to pass to the activity function. + A durable task that completes when the called activity function completes or fails. + + The retry option object is null. + + + The specified function does not exist, is disabled, or is not an orchestrator function. + + + The current thread is different than the thread which started the orchestrator execution. + + + The activity function failed with an unhandled exception. + + Signals an entity to perform an operation, without waiting for a response. Any result or exception is ignored (fire and forget). @@ -1429,6 +1415,22 @@ The instance id of the new orchestration. + + + Create an entity proxy. + + The target entity key. + Entity interface. + Entity proxy. + + + + Create an entity proxy. + + The target entity. + Entity interface. + Entity proxy. + Formerly, the abstract base class for DurableOrchestrationContext. @@ -1488,6 +1490,16 @@ JSON representation of configuration to emit in telemetry. + + + Value of maximum durable timer delay. Used for long running durable timers. + + + + + Interval time used for long running timers. + + @@ -1667,6 +1679,13 @@ The error message if the timespan is invalid. A boolean indicating whether the time interval is valid. + + + Returns true if the stored connection string, ConnectionName, matches the input DurabilityProvider ConnectionName. + + The DurabilityProvider used to check for matching connection string names. + A boolean indicating whether the connection names match. + Attribute used to bind a function parameter to a , , or instance. @@ -1999,6 +2018,30 @@ Not used. An activity shim that delegates execution to an activity function. + + + This DTFx activity middleware allows us to add context to the activity function shim + before it actually starts running. + + A property bag containing useful DTFx context. + The handler for running the next middleware in the pipeline. + + + + This DTFx orchestration middleware allows us to initialize Durable Functions-specific context + and make the execution happen in a way that plays nice with the Azure Functions execution pipeline. + + A property bag containing useful DTFx context. + The handler for running the next middleware in the pipeline. + + + + This DTFx orchestration middleware (for entities) allows us to add context and set state + to the entity shim orchestration before it starts executing the actual entity logic. + + A property bag containing useful DTFx context. + The handler for running the next middleware in the pipeline. + Gets a using configuration from a instance. @@ -2273,109 +2316,6 @@ The System.Runtime.Serialization.SerializationInfo that holds the serialized object data about the exception being thrown. The System.Runtime.Serialization.StreamingContext that contains contextual information about the source or destination. - - - Defines convenient overloads for creating entity proxy, for all the contexts. - - - - - Signals an entity to perform an operation. - - Entity interface. - orchestration client. - The target entity key. - A delegate that performs the desired operation on the entity. - A task that completes when the message has been reliably enqueued. - - - - Signals an entity to perform an operation, at a specified time. - - Entity interface. - orchestration client. - The target entity key. - The time at which to start the operation. - A delegate that performs the desired operation on the entity. - A task that completes when the message has been reliably enqueued. - - - - Signals an entity to perform an operation. - - Entity interface. - orchestration client. - The target entity. - A delegate that performs the desired operation on the entity. - A task that completes when the message has been reliably enqueued. - - - - Signals an entity to perform an operation, at a specified time. - - Entity interface. - orchestration client. - The target entity. - The time at which to start the operation. - A delegate that performs the desired operation on the entity. - A task that completes when the message has been reliably enqueued. - - - - Create an entity proxy. - - orchestration context. - The target entity key. - Entity interface. - Entity proxy. - - - - Create an entity proxy. - - orchestration context. - The target entity. - Entity interface. - Entity proxy. - - - - Signals an entity to perform an operation. - - entity context. - The target entity key. - A delegate that performs the desired operation on the entity. - Entity interface. - - - - Signals an entity to perform an operation, at a specified time. - - entity context. - The target entity key. - The time at which to start the operation. - A delegate that performs the desired operation on the entity. - Entity interface. - - - - Signals an entity to perform an operation. - - entity context. - The target entity. - A delegate that performs the desired operation on the entity. - Entity interface. - - - - Signals an entity to perform an operation, at a specified time. - - entity context. - The target entity. - The time at which to start the operation. - A delegate that performs the desired operation on the entity. - Entity interface. - Provides the base implementation for the entity proxy. @@ -2909,6 +2849,11 @@ Task activity implementation which delegates the implementation to a function. + + + The DTFx-generated, auto-incrementing ID that uniquely identifies this activity function execution. + + Common functionality of and . @@ -2996,12 +2941,34 @@ Task orchestration implementation which delegates the orchestration implementation to a function. + + + Configuration options for Managed Identity. + + + + + Initializes a new instance of the class. + + The host of the Azure Active Directory authority. + The tenant id of the user to authenticate. + + + + The host of the Azure Active Directory authority. The default is https://login.microsoftonline.com/. + + + + + The tenant id of the user to authenticate. + + Token Source implementation for Azure Managed Identities. - + Initializes a new instance of the class. @@ -3009,6 +2976,7 @@ The Azure Active Directory resource identifier of the web API being invoked. For example, https://management.core.windows.net/ or https://graph.microsoft.com/. + Optional Azure credential options to use when authenticating. @@ -3016,6 +2984,11 @@ For example, https://management.core.windows.net/ or https://graph.microsoft.com/. + + + The azure credential options that a user can configure when authenticating. + + @@ -3136,6 +3109,15 @@ Maximum interval for polling control and work-item queues. + + + Determines whether or not to use the old partition management strategy, or the new + strategy that is more resilient to split brain problems, at the potential expense + of scale out performance. + + A boolean indicating whether we use the legacy partition strategy. Defaults to true + but this will change to false once the new partition management strategy is fully tested. + Throws an exception if the provided hub name violates any naming conventions for the storage provider. @@ -3302,6 +3284,16 @@ only after an entire batch of operations completes. + + + If true, takes a lease on the task hub container, allowing for only one app to process messages in a task hub at a time. + + + + + If UseAppLease is true, gets or sets the AppLeaaseOptions used for acquiring the lease to start the application. + + Sets HubName to a value that is considered a default value. diff --git a/src/WebJobs.Extensions.DurableTask/Microsoft.Azure.WebJobs.Extensions.DurableTask.xml b/src/WebJobs.Extensions.DurableTask/Microsoft.Azure.WebJobs.Extensions.DurableTask.xml index 149c8a5b5..32766f6fa 100644 --- a/src/WebJobs.Extensions.DurableTask/Microsoft.Azure.WebJobs.Extensions.DurableTask.xml +++ b/src/WebJobs.Extensions.DurableTask/Microsoft.Azure.WebJobs.Extensions.DurableTask.xml @@ -93,10 +93,10 @@ - + - + @@ -123,10 +123,7 @@ - - - - + @@ -141,6 +138,24 @@ + + + + + + + + + + + + + + + + + + Common functionality used by both @@ -152,6 +167,15 @@ Context object passed to application code executing entity operations. + + + + + + + + + Parameter data for orchestration bindings that can be used to schedule function-based activities. @@ -254,425 +278,52 @@ - - - Defines convenient overloads for calling the context methods, for all the contexts. - - - - - Schedules an activity function named for execution. - - The context object. - The name of the activity function to call. - The JSON-serializeable input to pass to the activity function. - A durable task that completes when the called function completes or fails. - - The specified function does not exist, is disabled, or is not an orchestrator function. - - - The current thread is different than the thread which started the orchestrator execution. - - - The activity function failed with an unhandled exception. - - - - - Schedules an activity function named for execution with retry options. - - The context object. - The name of the activity function to call. - The retry option for the activity function. - The JSON-serializeable input to pass to the activity function. - A durable task that completes when the called activity function completes or fails. - - The retry option object is null. - - - The specified function does not exist, is disabled, or is not an orchestrator function. - - - The current thread is different than the thread which started the orchestrator execution. - - - The activity function failed with an unhandled exception. - - - - - Schedules an orchestrator function named for execution. - - The context object. - The name of the orchestrator function to call. - The JSON-serializeable input to pass to the orchestrator function. - A durable task that completes when the called orchestrator function completes or fails. - - The specified function does not exist, is disabled, or is not an orchestrator function. - - - The current thread is different than the thread which started the orchestrator execution. - - - The sub-orchestrator function failed with an unhandled exception. - - - - - Schedules an orchestrator function named for execution. - - The context object. - The name of the orchestrator function to call. - A unique ID to use for the sub-orchestration instance. - The JSON-serializeable input to pass to the orchestrator function. - A durable task that completes when the called orchestrator function completes or fails. - - The specified function does not exist, is disabled, or is not an orchestrator function. - - - The current thread is different than the thread which started the orchestrator execution. - - - The activity function failed with an unhandled exception. - - - - - Schedules an orchestration function named for execution. - - The return type of the scheduled orchestrator function. - The context object. - The name of the orchestrator function to call. - The JSON-serializeable input to pass to the orchestrator function. - A durable task that completes when the called orchestrator function completes or fails. - - The specified function does not exist, is disabled, or is not an orchestrator function. - - - The current thread is different than the thread which started the orchestrator execution. - - - The activity function failed with an unhandled exception. - - - - - Schedules an orchestrator function named for execution with retry options. - - The context object. - The name of the orchestrator function to call. - The retry option for the orchestrator function. - The JSON-serializeable input to pass to the orchestrator function. - A durable task that completes when the called orchestrator function completes or fails. - - The retry option object is null. - - - The specified function does not exist, is disabled, or is not an orchestrator function. - - - The current thread is different than the thread which started the orchestrator execution. - - - The activity function failed with an unhandled exception. - - - - - Schedules an orchestrator function named for execution with retry options. - - The context object. - The name of the orchestrator function to call. - The retry option for the orchestrator function. - A unique ID to use for the sub-orchestration instance. - The JSON-serializeable input to pass to the orchestrator function. - A durable task that completes when the called orchestrator function completes or fails. - - The retry option object is null. - - - The specified function does not exist, is disabled, or is not an orchestrator function. - - - The current thread is different than the thread which started the orchestrator execution. - - - The activity function failed with an unhandled exception. - - - - - Schedules an orchestrator function named for execution with retry options. - - The return type of the scheduled orchestrator function. - The context object. - The name of the orchestrator function to call. - The retry option for the orchestrator function. - The JSON-serializeable input to pass to the orchestrator function. - A durable task that completes when the called orchestrator function completes or fails. - - The retry option object is null. - - - The specified function does not exist, is disabled, or is not an orchestrator function. - - - The current thread is different than the thread which started the orchestrator execution. - - - The activity function failed with an unhandled exception. - - - - - Creates a durable timer that expires at a specified time. - - - All durable timers created using this method must either expire or be cancelled - using the before the orchestrator function completes. - Otherwise the underlying framework will keep the instance alive until the timer expires. - - The context object. - The time at which the timer should expire. - The CancellationToken to use for cancelling the timer. - A durable task that completes when the durable timer expires. - - - - Waits asynchronously for an event to be raised with name . - - - External clients can raise events to a waiting orchestration instance using - with the object parameter set to null. - - The context object. - The name of the event to wait for. - A durable task that completes when the external event is received. - - - - Waits asynchronously for an event to be raised with name . - - - External clients can raise events to a waiting orchestration instance using - with the object parameter set to null. - - The context object. - The name of the event to wait for. - The duration after which to throw a TimeoutException. - A durable task that completes when the external event is received. - - The external event was not received before the timeout expired. - - - - - Waits asynchronously for an event to be raised with name . - - - External clients can raise events to a waiting orchestration instance using - with the object parameter set to null. - - The context object. - The name of the event to wait for. - The duration after which to throw a TimeoutException. - The CancellationToken to use for cancelling 's internal timer. - A durable task that completes when the external event is received. - - The external event was not received before the timeout expired. - - - - - Waits asynchronously for an event to be raised with name and returns the event data. - - - External clients can raise events to a waiting orchestration instance using - . - - The context object. - The name of the event to wait for. - The duration after which to throw a TimeoutException. - Any serializeable type that represents the JSON event payload. - A durable task that completes when the external event is received. - - The external event was not received before the timeout expired. - - - - - Waits asynchronously for an event to be raised with name and returns the event data. - - - External clients can raise events to a waiting orchestration instance using - . - - The context object. - The name of the event to wait for. - The duration after which to return the value in the parameter. - The default value to return if the timeout expires before the external event is received. - Any serializeable type that represents the JSON event payload. - A durable task that completes when the external event is received, or returns the value of - if the timeout expires. - - - - Calls an operation on an entity and returns the result asynchronously. - - The JSON-serializable result type of the operation. - The context object. - The target entity. - The name of the operation. - A task representing the result of the operation. - - - - Calls an operation on an entity and waits for it to complete. - - The context object. - The target entity. - The name of the operation. - A task representing the completion of the operation on the entity. - - - - Creates an HTTP response which either contains a payload of management URLs for a non-completed instance - or contains the payload containing the output of the completed orchestration. - - - If the orchestration instance completes within the default 10 second timeout, then the HTTP response payload will - contain the output of the orchestration instance formatted as JSON. However, if the orchestration does not - complete within this timeout, then the HTTP response will be identical to that of the - API. - - The client object. - The HTTP request that triggered the current function. - The unique ID of the instance to check. - An HTTP response which may include a 202 and location header or a 200 with the durable function output in the response body. + + - - - Creates an HTTP response which either contains a payload of management URLs for a non-completed instance - or contains the payload containing the output of the completed orchestration. - - - If the orchestration instance completes within the default 10 second timeout, then the HTTP response payload will - contain the output of the orchestration instance formatted as JSON. However, if the orchestration does not - complete within this timeout, then the HTTP response will be identical to that of the - API. - - The client object. - The HTTP request that triggered the current function. - The unique ID of the instance to check. - An HTTP response which may include a 202 and location header or a 200 with the durable function output in the response body. + + - - - Creates an HTTP response which either contains a payload of management URLs for a non-completed instance - or contains the payload containing the output of the completed orchestration. - - - If the orchestration instance completes within the specified timeout, then the HTTP response payload will - contain the output of the orchestration instance formatted as JSON. However, if the orchestration does not - complete within the specified timeout, then the HTTP response will be identical to that of the - API. - - The client object. - The HTTP request that triggered the current function. - The unique ID of the instance to check. - Total allowed timeout for output from the durable function. The default value is 10 seconds. - An HTTP response which may include a 202 and location header or a 200 with the durable function output in the response body. + + - - - Creates an HTTP response which either contains a payload of management URLs for a non-completed instance - or contains the payload containing the output of the completed orchestration. - - - If the orchestration instance completes within the specified timeout, then the HTTP response payload will - contain the output of the orchestration instance formatted as JSON. However, if the orchestration does not - complete within the specified timeout, then the HTTP response will be identical to that of the - API. - - The client object. - The HTTP request that triggered the current function. - The unique ID of the instance to check. - Total allowed timeout for output from the durable function. The default value is 10 seconds. - An HTTP response which may include a 202 and location header or a 200 with the durable function output in the response body. + + - - - Starts a new execution of the specified orchestrator function. - - The client object. - The name of the orchestrator function to start. - The ID to use for the new orchestration instance. - A task that completes when the orchestration is started. The task contains the instance id of the started - orchestratation instance. - - The specified function does not exist, is disabled, or is not an orchestrator function. - + + - - - Starts a new execution of the specified orchestrator function. - - The client object. - The name of the orchestrator function to start. - JSON-serializeable input value for the orchestrator function. - The type of the input value for the orchestrator function. - A task that completes when the orchestration is started. The task contains the instance id of the started - orchestratation instance. - - The specified function does not exist, is disabled, or is not an orchestrator function. - + + - - - Starts a new execution of the specified orchestrator function. - - The client object. - The name of the orchestrator function to start. - A task that completes when the orchestration is started. The task contains the instance id of the started - orchestratation instance. - - The specified function does not exist, is disabled, or is not an orchestrator function. - + + - - - Sends an event notification message to a waiting orchestration instance. - - - - In order to handle the event, the target orchestration instance must be waiting for an - event named using the - API. - - - The instance id does not corespond to a valid orchestration instance. - The orchestration instance with the provided instance id is not running. - The client object. - The ID of the orchestration instance that will handle the event. - The name of the event. - A task that completes when the event notification message has been enqueued. + + - - - Gets the status of the specified orchestration instance. - - The client object. - The ID of the orchestration instance to query. - Returns a task which completes when the status has been fetched. + + - + + + + + + + + + + + + + + + + - Gets the status of the specified orchestration instance. + Defines convenient overloads for calling the context methods, for all the contexts. - The client object. - The ID of the orchestration instance to query. - Boolean marker for including execution history in the response. - Returns a task which completes when the status has been fetched. @@ -756,6 +407,44 @@ The name of the connection string associated with . A task that completes when the message has been reliably enqueued. + + + Signals an entity to perform an operation. + + Entity interface. + The target entity key. + A delegate that performs the desired operation on the entity. + A task that completes when the message has been reliably enqueued. + + + + Signals an entity to perform an operation, at a specified time. + + Entity interface. + The target entity key. + The time at which to start the operation. + A delegate that performs the desired operation on the entity. + A task that completes when the message has been reliably enqueued. + + + + Signals an entity to perform an operation. + + Entity interface. + The target entity. + A delegate that performs the desired operation on the entity. + A task that completes when the message has been reliably enqueued. + + + + Signals an entity to perform an operation, at a specified time. + + Entity interface. + The target entity. + The time at which to start the operation. + A delegate that performs the desired operation on the entity. + A task that completes when the message has been reliably enqueued. + Tries to read the current state of an entity. Returns default() if the entity does not @@ -880,6 +569,40 @@ The name of the operation. The input for the operation. + + + Signals an entity to perform an operation. + + The target entity key. + A delegate that performs the desired operation on the entity. + Entity interface. + + + + Signals an entity to perform an operation, at a specified time. + + The target entity key. + The time at which to start the operation. + A delegate that performs the desired operation on the entity. + Entity interface. + + + + Signals an entity to perform an operation. + + The target entity. + A delegate that performs the desired operation on the entity. + Entity interface. + + + + Signals an entity to perform an operation, at a specified time. + + The target entity. + The time at which to start the operation. + A delegate that performs the desired operation on the entity. + Entity interface. + Schedules a orchestration function named for execution./>. @@ -945,7 +668,7 @@ The ID of the orchestration instance to check. Instance of the class. - + Creates an HTTP response which either contains a payload of management URLs for a non-completed instance or contains the payload containing the output of the completed orchestration. @@ -962,7 +685,7 @@ The timeout between checks for output from the durable function. The default value is 1 second. An HTTP response which may include a 202 and location header or a 200 with the durable function output in the response body. - + Creates an HTTP response which either contains a payload of management URLs for a non-completed instance or contains the payload containing the output of the completed orchestration. @@ -979,6 +702,31 @@ The timeout between checks for output from the durable function. The default value is 1 second. An HTTP response which may include a 202 and location header or a 200 with the durable function output in the response body. + + + Starts a new execution of the specified orchestrator function. + + The name of the orchestrator function to start. + The ID to use for the new orchestration instance. + A task that completes when the orchestration is started. The task contains the instance id of the started + orchestratation instance. + + The specified function does not exist, is disabled, or is not an orchestrator function. + + + + + Starts a new execution of the specified orchestrator function. + + The name of the orchestrator function to start. + JSON-serializeable input value for the orchestrator function. + The type of the input value for the orchestrator function. + A task that completes when the orchestration is started. The task contains the instance id of the started + orchestratation instance. + + The specified function does not exist, is disabled, or is not an orchestrator function. + + Starts a new instance of the specified orchestrator function. @@ -1077,21 +825,14 @@ If set, fetch and return the input for the orchestration instance. Returns a task which completes when the status has been fetched. - - - Gets all the status of the orchestration instances. - - Cancellation token that can be used to cancel the status query operation. - Returns orchestration status for all instances. - - + Gets the status of all orchestration instances that match the specified conditions. - Return orchestration instances which were created after this DateTime. - Return orchestration instances which were created before this DateTime. - Return orchestration instances which matches the runtimeStatus. - Cancellation token that can be used to cancel the status query operation. + If specified, return orchestration instances which were created after this DateTime. + If specified, return orchestration instances which were created before this DateTime. + If specified, return orchestration instances which matches the runtimeStatus. + If specified, this ancellation token can be used to cancel the status query operation. Returns orchestration status for all instances. @@ -1241,6 +982,23 @@ The DurableHttpRequest used to make the HTTP call. A Result of the HTTP call. + + + Calls an operation on an entity and returns the result asynchronously. + + The JSON-serializable result type of the operation. + The target entity. + The name of the operation. + A task representing the result of the operation. + + + + Calls an operation on an entity and waits for it to complete. + + The target entity. + The name of the operation. + A task representing the completion of the operation on the entity. + Calls an operation on an entity, passing an argument, and returns the result asynchronously. @@ -1252,25 +1010,144 @@ A task representing the result of the operation. if the context already holds some locks, but not the one for . - + + + Calls an operation on an entity, passing an argument, and waits for it to complete. + + The target entity. + The name of the operation. + The input for the operation. + A task representing the completion of the operation on the entity. + if the context already holds some locks, but not the one for . + + + + Schedules an orchestrator function named for execution. + + The return type of the scheduled orchestrator function. + The name of the orchestrator function to call. + The JSON-serializeable input to pass to the orchestrator function. + A durable task that completes when the called orchestrator function completes or fails. + + The specified function does not exist, is disabled, or is not an orchestrator function. + + + The current thread is different than the thread which started the orchestrator execution. + + + The sub-orchestrator function failed with an unhandled exception. + + + + + Schedules an orchestration function named for execution. + + The return type of the scheduled orchestrator function. + The name of the orchestrator function to call. + A unique ID to use for the sub-orchestration instance. + The JSON-serializeable input to pass to the orchestrator function. + A durable task that completes when the called orchestrator function completes or fails. + + The specified function does not exist, is disabled, or is not an orchestrator function. + + + The current thread is different than the thread which started the orchestrator execution. + + + The activity function failed with an unhandled exception. + + + + + Schedules an orchestrator function named for execution. + + The name of the orchestrator function to call. + The JSON-serializeable input to pass to the orchestrator function. + A durable task that completes when the called orchestrator function completes or fails. + + The specified function does not exist, is disabled, or is not an orchestrator function. + + + The current thread is different than the thread which started the orchestrator execution. + + + The sub-orchestrator function failed with an unhandled exception. + + + + + Schedules an orchestrator function named for execution. + + The name of the orchestrator function to call. + A unique ID to use for the sub-orchestration instance. + The JSON-serializeable input to pass to the orchestrator function. + A durable task that completes when the called orchestrator function completes or fails. + + The specified function does not exist, is disabled, or is not an orchestrator function. + + + The current thread is different than the thread which started the orchestrator execution. + + + The activity function failed with an unhandled exception. + + + + + Schedules an orchestrator function named for execution with retry options. + + The return type of the scheduled orchestrator function. + The name of the orchestrator function to call. + The retry option for the orchestrator function. + A unique ID to use for the sub-orchestration instance. + The JSON-serializeable input to pass to the orchestrator function. + A durable task that completes when the called orchestrator function completes or fails. + + The retry option object is null. + + + The specified function does not exist, is disabled, or is not an orchestrator function. + + + The current thread is different than the thread which started the orchestrator execution. + + + The activity function failed with an unhandled exception. + + + - Calls an operation on an entity, passing an argument, and waits for it to complete. + Schedules an orchestrator function named for execution with retry options. - The target entity. - The name of the operation. - The input for the operation. - A task representing the completion of the operation on the entity. - if the context already holds some locks, but not the one for . + The name of the orchestrator function to call. + The retry option for the orchestrator function. + The JSON-serializeable input to pass to the orchestrator function. + A durable task that completes when the called orchestrator function completes or fails. + + The retry option object is null. + + + The specified function does not exist, is disabled, or is not an orchestrator function. + + + The current thread is different than the thread which started the orchestrator execution. + + + The activity function failed with an unhandled exception. + - + - Schedules an orchestration function named for execution. + Schedules an orchestrator function named for execution with retry options. - The return type of the scheduled orchestrator function. The name of the orchestrator function to call. + The retry option for the orchestrator function. A unique ID to use for the sub-orchestration instance. The JSON-serializeable input to pass to the orchestrator function. A durable task that completes when the called orchestrator function completes or fails. + + The retry option object is null. + The specified function does not exist, is disabled, or is not an orchestrator function. @@ -1281,14 +1158,13 @@ The activity function failed with an unhandled exception. - + Schedules an orchestrator function named for execution with retry options. The return type of the scheduled orchestrator function. The name of the orchestrator function to call. The retry option for the orchestrator function. - A unique ID to use for the sub-orchestration instance. The JSON-serializeable input to pass to the orchestrator function. A durable task that completes when the called orchestrator function completes or fails. @@ -1319,6 +1195,19 @@ The CancellationToken to use for cancelling the timer. A durable task that completes when the durable timer expires. + + + Creates a durable timer that expires at a specified time. + + + All durable timers created using this method must either expire or be cancelled + using the before the orchestrator function completes. + Otherwise the underlying framework will keep the instance alive until the timer expires. + + The time at which the timer should expire. + The CancellationToken to use for cancelling the timer. + A durable task that completes when the durable timer expires. + Waits asynchronously for an event to be raised with name and returns the event data. @@ -1331,23 +1220,48 @@ Any serializeable type that represents the JSON event payload. A durable task that completes when the external event is received. - + - Waits asynchronously for an event to be raised with name and returns the event data. + Waits asynchronously for an event to be raised with name . External clients can raise events to a waiting orchestration instance using - . + with the object parameter set to null. + + The name of the event to wait for. + A durable task that completes when the external event is received. + + + + Waits asynchronously for an event to be raised with name . + + + External clients can raise events to a waiting orchestration instance using + with the object parameter set to null. The name of the event to wait for. The duration after which to throw a TimeoutException. The CancellationToken to use for cancelling 's internal timer. - Any serializeable type that represents the JSON event payload. A durable task that completes when the external event is received. The external event was not received before the timeout expired. + + + Waits asynchronously for an event to be raised with name and returns the event data. + + + External clients can raise events to a waiting orchestration instance using + . + + The name of the event to wait for. + The duration of time to wait for the event. + The CancellationToken to use for cancelling 's internal timer. + Any serializeable type that represents the JSON event payload. + A durable task that completes when the external event is received, or throws a timeout exception"/> + if the timeout expires. + Waits asynchronously for an event to be raised with name and returns the event data. @@ -1357,8 +1271,9 @@ . The name of the event to wait for. - The duration after which to return the value in the parameter. - The default value to return if the timeout expires before the external event is received. + The duration of time to wait for the event. + If specified, the default value to return if the timeout expires before the external event is received. + Otherwise, a timeout exception will be thrown instead. The CancellationToken to use for cancelling 's internal timer. Any serializeable type that represents the JSON event payload. A durable task that completes when the external event is received, or returns the value of @@ -1415,6 +1330,23 @@ The activity function failed with an unhandled exception. + + + Schedules an activity function named for execution. + + The name of the activity function to call. + The JSON-serializeable input to pass to the activity function. + A durable task that completes when the called function completes or fails. + + The specified function does not exist, is disabled, or is not an orchestrator function. + + + The current thread is different than the thread which started the orchestrator execution. + + + The activity function failed with an unhandled exception. + + Schedules an activity function named for execution with retry options. @@ -1437,6 +1369,27 @@ The activity function failed with an unhandled exception. + + + Schedules an activity function named for execution with retry options. + + The name of the activity function to call. + The retry option for the activity function. + The JSON-serializeable input to pass to the activity function. + A durable task that completes when the called activity function completes or fails. + + The retry option object is null. + + + The specified function does not exist, is disabled, or is not an orchestrator function. + + + The current thread is different than the thread which started the orchestrator execution. + + + The activity function failed with an unhandled exception. + + Signals an entity to perform an operation, without waiting for a response. Any result or exception is ignored (fire and forget). @@ -1467,6 +1420,22 @@ The instance id of the new orchestration. + + + Create an entity proxy. + + The target entity key. + Entity interface. + Entity proxy. + + + + Create an entity proxy. + + The target entity. + Entity interface. + Entity proxy. + Formerly, the abstract base class for DurableOrchestrationContext. @@ -1526,6 +1495,16 @@ JSON representation of configuration to emit in telemetry. + + + Value of maximum durable timer delay. Used for long running durable timers. + + + + + Interval time used for long running timers. + + @@ -1705,6 +1684,13 @@ The error message if the timespan is invalid. A boolean indicating whether the time interval is valid. + + + Returns true if the stored connection string, ConnectionName, matches the input DurabilityProvider ConnectionName. + + The DurabilityProvider used to check for matching connection string names. + A boolean indicating whether the connection names match. + Attribute used to bind a function parameter to a , , or instance. @@ -2021,6 +2007,30 @@ Not used. An activity shim that delegates execution to an activity function. + + + This DTFx activity middleware allows us to add context to the activity function shim + before it actually starts running. + + A property bag containing useful DTFx context. + The handler for running the next middleware in the pipeline. + + + + This DTFx orchestration middleware allows us to initialize Durable Functions-specific context + and make the execution happen in a way that plays nice with the Azure Functions execution pipeline. + + A property bag containing useful DTFx context. + The handler for running the next middleware in the pipeline. + + + + This DTFx orchestration middleware (for entities) allows us to add context and set state + to the entity shim orchestration before it starts executing the actual entity logic. + + A property bag containing useful DTFx context. + The handler for running the next middleware in the pipeline. + Gets a using configuration from a instance. @@ -2317,109 +2327,6 @@ The System.Runtime.Serialization.SerializationInfo that holds the serialized object data about the exception being thrown. The System.Runtime.Serialization.StreamingContext that contains contextual information about the source or destination. - - - Defines convenient overloads for creating entity proxy, for all the contexts. - - - - - Signals an entity to perform an operation. - - Entity interface. - orchestration client. - The target entity key. - A delegate that performs the desired operation on the entity. - A task that completes when the message has been reliably enqueued. - - - - Signals an entity to perform an operation, at a specified time. - - Entity interface. - orchestration client. - The target entity key. - The time at which to start the operation. - A delegate that performs the desired operation on the entity. - A task that completes when the message has been reliably enqueued. - - - - Signals an entity to perform an operation. - - Entity interface. - orchestration client. - The target entity. - A delegate that performs the desired operation on the entity. - A task that completes when the message has been reliably enqueued. - - - - Signals an entity to perform an operation, at a specified time. - - Entity interface. - orchestration client. - The target entity. - The time at which to start the operation. - A delegate that performs the desired operation on the entity. - A task that completes when the message has been reliably enqueued. - - - - Create an entity proxy. - - orchestration context. - The target entity key. - Entity interface. - Entity proxy. - - - - Create an entity proxy. - - orchestration context. - The target entity. - Entity interface. - Entity proxy. - - - - Signals an entity to perform an operation. - - entity context. - The target entity key. - A delegate that performs the desired operation on the entity. - Entity interface. - - - - Signals an entity to perform an operation, at a specified time. - - entity context. - The target entity key. - The time at which to start the operation. - A delegate that performs the desired operation on the entity. - Entity interface. - - - - Signals an entity to perform an operation. - - entity context. - The target entity. - A delegate that performs the desired operation on the entity. - Entity interface. - - - - Signals an entity to perform an operation, at a specified time. - - entity context. - The target entity. - The time at which to start the operation. - A delegate that performs the desired operation on the entity. - Entity interface. - Provides the base implementation for the entity proxy. @@ -2982,6 +2889,11 @@ Task activity implementation which delegates the implementation to a function. + + + The DTFx-generated, auto-incrementing ID that uniquely identifies this activity function execution. + + Common functionality of and . @@ -3069,12 +2981,34 @@ Task orchestration implementation which delegates the orchestration implementation to a function. + + + Configuration options for Managed Identity. + + + + + Initializes a new instance of the class. + + The host of the Azure Active Directory authority. + The tenant id of the user to authenticate. + + + + The host of the Azure Active Directory authority. The default is https://login.microsoftonline.com/. + + + + + The tenant id of the user to authenticate. + + Token Source implementation for Azure Managed Identities. - + Initializes a new instance of the class. @@ -3082,6 +3016,7 @@ The Azure Active Directory resource identifier of the web API being invoked. For example, https://management.core.windows.net/ or https://graph.microsoft.com/. + Optional Azure credential options to use when authenticating. @@ -3089,6 +3024,11 @@ For example, https://management.core.windows.net/ or https://graph.microsoft.com/. + + + The azure credential options that a user can configure when authenticating. + + @@ -3209,6 +3149,15 @@ Maximum interval for polling control and work-item queues. + + + Determines whether or not to use the old partition management strategy, or the new + strategy that is more resilient to split brain problems, at the potential expense + of scale out performance. + + A boolean indicating whether we use the legacy partition strategy. Defaults to true + but this will change to false once the new partition management strategy is fully tested. + Throws an exception if the provided hub name violates any naming conventions for the storage provider. @@ -3375,6 +3324,16 @@ only after an entire batch of operations completes. + + + If true, takes a lease on the task hub container, allowing for only one app to process messages in a task hub at a time. + + + + + If UseAppLease is true, gets or sets the AppLeaaseOptions used for acquiring the lease to start the application. + + Sets HubName to a value that is considered a default value. diff --git a/src/WebJobs.Extensions.DurableTask/Options/AzureStorageOptions.cs b/src/WebJobs.Extensions.DurableTask/Options/AzureStorageOptions.cs index c6968a93e..a0feb0308 100644 --- a/src/WebJobs.Extensions.DurableTask/Options/AzureStorageOptions.cs +++ b/src/WebJobs.Extensions.DurableTask/Options/AzureStorageOptions.cs @@ -120,6 +120,15 @@ public class AzureStorageOptions /// Maximum interval for polling control and work-item queues. public TimeSpan MaxQueuePollingInterval { get; set; } = TimeSpan.FromSeconds(30); + /// + /// Determines whether or not to use the old partition management strategy, or the new + /// strategy that is more resilient to split brain problems, at the potential expense + /// of scale out performance. + /// + /// A boolean indicating whether we use the legacy partition strategy. Defaults to true + /// but this will change to false once the new partition management strategy is fully tested. + public bool UseLegacyPartitionManagement { get; set; } = true; + /// /// Throws an exception if the provided hub name violates any naming conventions for the storage provider. /// diff --git a/src/WebJobs.Extensions.DurableTask/Options/DurableTaskOptions.cs b/src/WebJobs.Extensions.DurableTask/Options/DurableTaskOptions.cs index d9f077f6f..26183836b 100644 --- a/src/WebJobs.Extensions.DurableTask/Options/DurableTaskOptions.cs +++ b/src/WebJobs.Extensions.DurableTask/Options/DurableTaskOptions.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Net.Http; +using DurableTask.AzureStorage.Partitioning; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Newtonsoft.Json.Linq; @@ -195,6 +196,16 @@ public string HubName /// public bool RollbackEntityOperationsOnExceptions { get; set; } = true; + /// + /// If true, takes a lease on the task hub container, allowing for only one app to process messages in a task hub at a time. + /// + public bool UseAppLease { get; set; } = true; + + /// + /// If UseAppLease is true, gets or sets the AppLeaaseOptions used for acquiring the lease to start the application. + /// + public AppLeaseOptions AppLeaseOptions { get; set; } = AppLeaseOptions.DefaultOptions; + // Used for mocking the lifecycle notification helper. internal HttpMessageHandler NotificationHandler { get; set; } diff --git a/src/WebJobs.Extensions.DurableTask/OutOfProcExceptionHelpers.cs b/src/WebJobs.Extensions.DurableTask/OutOfProcExceptionHelpers.cs index 5415a26c9..0103de609 100644 --- a/src/WebJobs.Extensions.DurableTask/OutOfProcExceptionHelpers.cs +++ b/src/WebJobs.Extensions.DurableTask/OutOfProcExceptionHelpers.cs @@ -52,6 +52,12 @@ public static bool TryGetExceptionWithFriendlyMessage(Exception ex, out Exceptio */ public static bool TryExtractOutOfProcStateJson(Exception ex, out string stateJson) { + if (ex == null) + { + stateJson = null; + return false; + } + if (!TryGetFullOutOfProcMessage(ex, out string outOfProcMessage)) { stateJson = null; diff --git a/src/WebJobs.Extensions.DurableTask/WebJobs.Extensions.DurableTask.csproj b/src/WebJobs.Extensions.DurableTask/WebJobs.Extensions.DurableTask.csproj index 5a05d59d0..e81f05cfe 100644 --- a/src/WebJobs.Extensions.DurableTask/WebJobs.Extensions.DurableTask.csproj +++ b/src/WebJobs.Extensions.DurableTask/WebJobs.Extensions.DurableTask.csproj @@ -5,8 +5,8 @@ Microsoft.Azure.WebJobs.Extensions.DurableTask Microsoft.Azure.WebJobs.Extensions.DurableTask 2 - 2 - 2 + 3 + 0 $(MajorVersion).$(MinorVersion).$(PatchVersion) $(MajorVersion).$(MinorVersion).$(PatchVersion) $(MajorVersion).0.0.0 @@ -19,6 +19,8 @@ true embedded false + + NU5125 diff --git a/test/Common/ClientFunctions.cs b/test/Common/ClientFunctions.cs index 47d88359c..db221d133 100644 --- a/test/Common/ClientFunctions.cs +++ b/test/Common/ClientFunctions.cs @@ -77,14 +77,14 @@ public static void GetOrchestrationClientBindingTest( clientRef[0] = client; } +#pragma warning disable CS0618 // Type or member is obsolete ([OrchestrationClient]) + /// /// Helper function for the IDurableOrchestrationClientBindingBackComp test. Gets an IDurableEntityClient. /// [NoAutomaticTrigger] public static void GetEntityClientBindingBackCompTest( -#pragma warning disable CS0618 // Type or member is obsolete [OrchestrationClient] IDurableEntityClient client, -#pragma warning restore CS0618 // Type or member is obsolete IDurableEntityClient[] clientRef) { clientRef[0] = client; @@ -95,13 +95,12 @@ public static void GetEntityClientBindingBackCompTest( /// [NoAutomaticTrigger] public static void GetOrchestrationClientBindingBackCompTest( -#pragma warning disable CS0618 // Type or member is obsolete [OrchestrationClient] IDurableOrchestrationClient client, -#pragma warning restore CS0618 // Type or member is obsolete IDurableOrchestrationClient[] clientRef) { clientRef[0] = client; } +#pragma warning restore CS0618 // Type or member is obsolete ([OrchestrationClient]) /// /// Helper function for testing the JSON data that gets sent to out-of-proc client functions. diff --git a/test/Common/DurableClientBaseTests.cs b/test/Common/DurableClientBaseTests.cs index 4afe307ef..4d46b00db 100644 --- a/test/Common/DurableClientBaseTests.cs +++ b/test/Common/DurableClientBaseTests.cs @@ -13,6 +13,7 @@ using Microsoft.AspNetCore.Mvc; #endif using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Microsoft.Extensions.Primitives; using Moq; @@ -23,36 +24,24 @@ namespace Microsoft.Azure.WebJobs.Extensions.DurableTask.Tests { public class DurableClientBaseTests { - [Fact] - [Trait("Category", PlatformSpecificHelpers.TestCategory)] - public async Task StartNewAsync_is_calling_overload_method() - { - var instanceId = Guid.NewGuid().ToString(); - const string functionName = "sampleFunction"; - var durableOrchestrationClientBaseMock = new Mock { CallBase = true }; - durableOrchestrationClientBaseMock.Setup(x => x.StartNewAsync(functionName, string.Empty, null)).ReturnsAsync(instanceId); - - var result = await durableOrchestrationClientBaseMock.Object.StartNewAsync(functionName); - result.Should().Be(instanceId); - - result = await durableOrchestrationClientBaseMock.Object.StartNewAsync(functionName, null); - result.Should().Be(instanceId); - } - - [Fact] + [Theory] [Trait("Category", PlatformSpecificHelpers.TestCategory)] - public async Task StartNewAsync_is_calling_overload_method_with_specific_instance_id() + [InlineData("@invalid")] + [InlineData("/invalid")] + [InlineData("invalid\\")] + [InlineData("invalid#")] + [InlineData("invalid?")] + [InlineData("invalid\t")] + [InlineData("invalid\n")] + public async Task StartNewAsync_InvalidInstanceId_ThrowsException(string instanceId) { - var instanceId = "testInstance"; - const string functionName = "sampleFunction"; - var durableOrchestrationClientBaseMock = new Mock { CallBase = true }; - durableOrchestrationClientBaseMock.Setup(x => x.StartNewAsync(functionName, instanceId, null)).ReturnsAsync(instanceId); - - var result = await durableOrchestrationClientBaseMock.Object.StartNewAsync(functionName, instanceId); - result.Should().Be(instanceId); + var orchestrationServiceClientMock = new Mock(); + orchestrationServiceClientMock.Setup(x => x.GetOrchestrationStateAsync(It.IsAny(), It.IsAny())).ReturnsAsync(GetInvalidInstanceState()); + var storageProvider = new DurabilityProvider("test", new Mock().Object, orchestrationServiceClientMock.Object, "test"); + var durableExtension = GetDurableTaskConfig(); + var durableClient = (IDurableOrchestrationClient)new DurableClient(storageProvider, durableExtension, durableExtension.HttpApiHandler, new DurableClientAttribute { }); - result = await durableOrchestrationClientBaseMock.Object.StartNewAsync(functionName, instanceId, null); - result.Should().Be(instanceId); + await Assert.ThrowsAnyAsync(async () => await durableClient.StartNewAsync("anyOrchestratorFunction", instanceId, new { message = "any obj" })); } [Theory] @@ -64,15 +53,16 @@ public async Task StartNewAsync_is_calling_overload_method_with_specific_instanc [InlineData("invalid?")] [InlineData("invalid\t")] [InlineData("invalid\n")] - public async Task StartNewAsync_InvalidInstanceId_ThrowsException(string instanceId) + public async Task SignalEntity_InvalidEntityKey_ThrowsException(string entityKey) { var orchestrationServiceClientMock = new Mock(); orchestrationServiceClientMock.Setup(x => x.GetOrchestrationStateAsync(It.IsAny(), It.IsAny())).ReturnsAsync(GetInvalidInstanceState()); var storageProvider = new DurabilityProvider("test", new Mock().Object, orchestrationServiceClientMock.Object, "test"); var durableExtension = GetDurableTaskConfig(); - var durableClient = (IDurableOrchestrationClient)new DurableClient(storageProvider, durableExtension, durableExtension.HttpApiHandler, new DurableClientAttribute { }); + var durableClient = (IDurableEntityClient)new DurableClient(storageProvider, durableExtension, durableExtension.HttpApiHandler, new DurableClientAttribute { }); - await Assert.ThrowsAnyAsync(async () => await durableClient.StartNewAsync("anyOrchestratorFunction", instanceId, new { message = "any obj" })); + var entityId = new EntityId("test", entityKey); + await Assert.ThrowsAnyAsync(async () => await durableClient.SignalEntityAsync(entityId, "test")); } [Fact] @@ -241,7 +231,11 @@ private static DurableTaskExtension GetDurableTaskConfig() var wrappedOptions = new OptionsWrapper(options); var nameResolver = TestHelpers.GetTestNameResolver(); var connectionStringResolver = new TestConnectionStringResolver(); - var serviceFactory = new AzureStorageDurabilityProviderFactory(wrappedOptions, connectionStringResolver, nameResolver); + var serviceFactory = new AzureStorageDurabilityProviderFactory( + wrappedOptions, + connectionStringResolver, + nameResolver, + NullLoggerFactory.Instance); return new DurableTaskExtension( wrappedOptions, new LoggerFactory(), diff --git a/test/Common/DurableContextExtensionsTests.cs b/test/Common/DurableContextExtensionsTests.cs deleted file mode 100644 index 124596b8c..000000000 --- a/test/Common/DurableContextExtensionsTests.cs +++ /dev/null @@ -1,186 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. See LICENSE in the project root for license information. - -using System; -using System.Threading; -using System.Threading.Tasks; -using FluentAssertions; -using Moq; -using Xunit; - -namespace Microsoft.Azure.WebJobs.Extensions.DurableTask.Tests -{ - public class DurableContextExtensionsTests - { - private const string FunctionName = "sampleFunction"; - private readonly int stateValueTen = 10; - private readonly Task taskFromTen = Task.FromResult(10); - private readonly int stateValueFive = 5; - private readonly Task intResultTask = Task.FromResult(5); - private readonly object inputObject = (object)3; - private readonly string operationName = "myop"; - private readonly EntityId entityId = new EntityId("a", "b"); - private readonly TimeSpan timeSpan = TimeSpan.FromMinutes(1); - - [Fact] - [Trait("Category", PlatformSpecificHelpers.TestCategory)] - public void CallActivityAsync_is_calling_extension_method() - { - var durableOrchestrationContextBaseMock = new Mock { }; - durableOrchestrationContextBaseMock.Setup(x => x.CallActivityAsync(FunctionName, this.inputObject)).Returns(this.taskFromTen); - var result = durableOrchestrationContextBaseMock.Object.CallActivityAsync(FunctionName, this.inputObject); - result.Should().Be(this.taskFromTen); - } - - [Fact] - [Trait("Category", PlatformSpecificHelpers.TestCategory)] - public async Task CallActivityWithRetryAsync_is_calling_extension_method() - { - var retryOptions = new RetryOptions(TimeSpan.FromSeconds(10), 5); - var durableOrchestrationContextBaseMock = new Mock { }; - durableOrchestrationContextBaseMock - .Setup(x => x.CallActivityWithRetryAsync(FunctionName, retryOptions, this.inputObject)) - .Returns(this.taskFromTen); - var result = durableOrchestrationContextBaseMock.Object.CallActivityWithRetryAsync(FunctionName, retryOptions, this.inputObject); - var resultValue = await (Task)result; - resultValue.Should().Be(this.stateValueTen); - } - - [Fact] - [Trait("Category", PlatformSpecificHelpers.TestCategory)] - public async Task CallSubOrchestratorAsync_is_calling_extension_method() - { - var durableOrchestrationContextBaseMock = new Mock { }; - durableOrchestrationContextBaseMock.Setup(x => x.CallSubOrchestratorAsync(FunctionName, null, this.inputObject)) - .Returns(this.taskFromTen); - var result = durableOrchestrationContextBaseMock.Object.CallSubOrchestratorAsync(FunctionName, this.inputObject); - var resultValue = await (Task)result; - resultValue.Should().Be(this.stateValueTen); - } - - [Fact] - [Trait("Category", PlatformSpecificHelpers.TestCategory)] - public async Task CallSubOrchestratorAsync_with_instanceId_is_calling_extension_method() - { - var durableOrchestrationContextBaseMock = new Mock { CallBase = true }; - var instanceId = Guid.NewGuid().ToString(); - durableOrchestrationContextBaseMock.Setup(x => x.CallSubOrchestratorAsync(FunctionName, instanceId, this.inputObject)) - .Returns(this.taskFromTen); - var result = durableOrchestrationContextBaseMock.Object.CallSubOrchestratorAsync(FunctionName, instanceId, this.inputObject); - var resultValue = await (Task)result; - resultValue.Should().Be(this.stateValueTen); - } - - [Fact] - [Trait("Category", PlatformSpecificHelpers.TestCategory)] - public async Task CallSubOrchestratorAsync_typed_task_is_calling_extension_method() - { - var durableOrchestrationContextBaseMock = new Mock { }; - durableOrchestrationContextBaseMock.Setup(x => x.CallSubOrchestratorAsync(FunctionName, null, this.inputObject)).Returns(this.intResultTask); - var result = await durableOrchestrationContextBaseMock.Object.CallSubOrchestratorAsync(FunctionName, this.inputObject); - result.Should().Be(this.stateValueFive); - } - - [Fact] - [Trait("Category", PlatformSpecificHelpers.TestCategory)] - public async Task CallSubOrchestratorWithRetryAsync_with_4_parameters_is_calling_extension_method() - { - var retryOptions = new RetryOptions(TimeSpan.FromSeconds(10), 5); - var durableOrchestrationContextBaseMock = new Mock { }; - durableOrchestrationContextBaseMock - .Setup(x => x.CallSubOrchestratorWithRetryAsync(FunctionName, retryOptions, null, this.inputObject)) - .Returns(this.taskFromTen); - var result = durableOrchestrationContextBaseMock.Object.CallSubOrchestratorWithRetryAsync(FunctionName, retryOptions, this.inputObject); - var resultValue = await (Task)result; - resultValue.Should().Be(this.stateValueTen); - } - - [Fact] - [Trait("Category", PlatformSpecificHelpers.TestCategory)] - public async Task CallSubOrchestratorWithRetryAsync_with_5_parameters_is_calling_extension_method() - { - var retryOptions = new RetryOptions(TimeSpan.FromSeconds(10), 5); - var instanceId = Guid.NewGuid().ToString(); - var durableOrchestrationContextBaseMock = new Mock { }; - durableOrchestrationContextBaseMock - .Setup(x => x.CallSubOrchestratorWithRetryAsync(FunctionName, retryOptions, instanceId, this.inputObject)) - .Returns(this.taskFromTen); - var result = durableOrchestrationContextBaseMock.Object.CallSubOrchestratorWithRetryAsync(FunctionName, retryOptions, instanceId, this.inputObject); - var resultValue = await (Task)result; - resultValue.Should().Be(this.stateValueTen); - } - - [Fact] - [Trait("Category", PlatformSpecificHelpers.TestCategory)] - public async Task CallSubOrchestratorWithRetryAsync_typed_task_is_calling_extension_method() - { - var durableOrchestrationContextBaseMock = new Mock { }; - durableOrchestrationContextBaseMock.Setup(x => x.CallSubOrchestratorAsync(FunctionName, null, this.inputObject)).ReturnsAsync(this.stateValueFive); - var result = await durableOrchestrationContextBaseMock.Object.CallSubOrchestratorAsync(FunctionName, this.inputObject); - result.Should().Be(this.stateValueFive); - } - - [Fact] - [Trait("Category", PlatformSpecificHelpers.TestCategory)] - public async Task WaitForExternalEvent_is_calling_extension_method() - { - var dateTime = DateTime.Now; - var cancelToken = CancellationToken.None; - var durableOrchestrationContextBaseMock = new Mock { }; - durableOrchestrationContextBaseMock.Setup(x => x.WaitForExternalEvent(this.operationName)).Returns(this.taskFromTen); - var result = durableOrchestrationContextBaseMock.Object.WaitForExternalEvent(this.operationName); - var resultValue = await (Task)result; - resultValue.Should().Be(this.stateValueTen); - } - - [Fact] - [Trait("Category", PlatformSpecificHelpers.TestCategory)] - public async Task WaitForExternalEvent_with_timeout_is_calling_extension_method() - { - var dateTime = DateTime.Now; - var cancelToken = CancellationToken.None; - var durableOrchestrationContextBaseMock = new Mock { }; - durableOrchestrationContextBaseMock.Setup(x => x.WaitForExternalEvent(this.operationName, this.timeSpan, CancellationToken.None)).Returns(this.taskFromTen); - var result = durableOrchestrationContextBaseMock.Object.WaitForExternalEvent(this.operationName, this.timeSpan); - var resultValue = await (Task)result; - resultValue.Should().Be(this.stateValueTen); - } - - [Fact] - [Trait("Category", PlatformSpecificHelpers.TestCategory)] - public async Task CreateTimer_is_calling_extension_method() - { - var dateTime = DateTime.Now; - var cancelToken = CancellationToken.None; - var durableOrchestrationContextBaseMock = new Mock { }; - durableOrchestrationContextBaseMock.Setup(x => x.CreateTimer(dateTime, null, cancelToken)).ReturnsAsync(this.stateValueFive); - var result = durableOrchestrationContextBaseMock.Object.CreateTimer(dateTime, cancelToken); - var resultValue = await (Task)result; - resultValue.Should().Be(this.stateValueFive); - } - - [Fact] - [Trait("Category", PlatformSpecificHelpers.TestCategory)] - public async Task CallEntityAsync_without_content_is_calling_extension_method() - { - var durableOrchestrationContextBaseMock = new Mock { }; - durableOrchestrationContextBaseMock.Setup(x => x.CallEntityAsync(this.entityId, this.operationName, null)) - .Returns(this.taskFromTen); - var result = durableOrchestrationContextBaseMock.Object.CallEntityAsync(this.entityId, this.operationName); - var resultValue = await (Task)result; - resultValue.Should().Be(this.stateValueTen); - } - - [Fact] - [Trait("Category", PlatformSpecificHelpers.TestCategory)] - public Task CallEntityAsync_with_resulttype_without_content_is_calling_extension_method() - { - var durableOrchestrationContextBaseMock = new Mock { }; - durableOrchestrationContextBaseMock.Setup(x => x.CallEntityAsync(this.entityId, this.operationName, null)) - .Returns(this.intResultTask); - var result = durableOrchestrationContextBaseMock.Object.CallEntityAsync(this.entityId, this.operationName); - result.Should().Be(this.intResultTask); - return result; - } - } -} diff --git a/test/Common/DurableHttpTests.cs b/test/Common/DurableHttpTests.cs index b36ad97bc..578ea6f6f 100644 --- a/test/Common/DurableHttpTests.cs +++ b/test/Common/DurableHttpTests.cs @@ -12,6 +12,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using Azure.Identity; using Microsoft.Azure.WebJobs.Host.TestCommon; using Microsoft.Diagnostics.Tracing; using Microsoft.Extensions.Primitives; @@ -120,6 +121,157 @@ public void DeserializeCallActivity() Assert.Empty(customHeaderValues); } + [Fact] + [Trait("Category", PlatformSpecificHelpers.TestCategory)] + public void SerializeManagedIdentityOptions() + { + // Part 1: Check if ManagedIdentityOptions is correctly serialized with TestDurableHttpRequest + var expectedTestDurableHttpRequestJson = @" +{ + ""HttpMethod"": { + ""Method"": ""GET"" + }, + ""Uri"": ""https://www.dummy-url.com"", + ""Headers"": { + ""Accept"": ""application/json"" + }, + ""Content"": null, + ""TokenSource"": { + ""$type"": ""Microsoft.Azure.WebJobs.Extensions.DurableTask.Tests.DurableHttpTests+MockTokenSource, WebJobs.Extensions.DurableTask.Tests.V2"", + ""testToken"": ""dummy token"", + ""options"": { + ""authorityhost"": ""https://dummy.login.microsoftonline.com/"", + ""tenantid"": ""tenant_id"" + } + }, + ""AsynchronousPatternEnabled"": true +}"; + + Dictionary headers = new Dictionary(); + headers.Add("Accept", "application/json"); + + ManagedIdentityOptions options = new ManagedIdentityOptions(); + options.AuthorityHost = new Uri("https://dummy.login.microsoftonline.com/"); + options.TenantId = "tenant_id"; + + MockTokenSource mockTokenSource = new MockTokenSource("dummy token", options); + + TestDurableHttpRequest request = new TestDurableHttpRequest( + httpMethod: HttpMethod.Get, + headers: headers, + tokenSource: mockTokenSource); + + string serializedTestDurableHttpRequest = JsonConvert.SerializeObject(request); + + Assert.True(JToken.DeepEquals(JObject.Parse(expectedTestDurableHttpRequestJson), JObject.Parse(serializedTestDurableHttpRequest))); + + // Part 2: Check if ManagedIdentityOptions is correctly serialized with DurableHttpRequest + var expectedDurableHttpRequestJson = @" +{ + ""method"": ""GET"", + ""uri"": ""https://www.dummy-url.com"", + ""headers"": { + ""Accept"": ""application/json"" + }, + ""content"": null, + ""tokenSource"": { + ""kind"": ""AzureManagedIdentity"", + ""resource"": ""dummy url"", + ""options"": { + ""authorityhost"": ""https://dummy.login.microsoftonline.com/"", + ""tenantid"": ""tenant_id"" + } + }, + ""asynchronousPatternEnabled"": true +}"; + ManagedIdentityTokenSource managedIdentityTokenSource = new ManagedIdentityTokenSource("dummy url", options); + TestDurableHttpRequest testDurableHttpRequest = new TestDurableHttpRequest( + httpMethod: HttpMethod.Get, + headers: headers, + tokenSource: managedIdentityTokenSource); + + DurableHttpRequest durableHttpRequest = TestOrchestrations.ConvertTestRequestToDurableHttpRequest(testDurableHttpRequest); + string serializedDurableHttpRequest = JsonConvert.SerializeObject(durableHttpRequest); + + Assert.True(JToken.DeepEquals(JObject.Parse(expectedDurableHttpRequestJson), JObject.Parse(serializedDurableHttpRequest))); + } + + [Fact] + [Trait("Category", PlatformSpecificHelpers.TestCategory)] + public void SerializeDurableHttpRequestWithoutManagedIdentityOptions() + { + var expectedDurableHttpRequestJson = @" +{ + ""method"": ""GET"", + ""uri"": ""https://www.dummy-url.com"", + ""headers"": { + ""Accept"": ""application/json"" + }, + ""content"": null, + ""tokenSource"": { + ""kind"": ""AzureManagedIdentity"", + ""resource"": ""dummy url"" + }, + ""asynchronousPatternEnabled"": true +}"; + + Dictionary headers = new Dictionary(); + headers.Add("Accept", "application/json"); + + ManagedIdentityTokenSource managedIdentityTokenSource = new ManagedIdentityTokenSource("dummy url"); + TestDurableHttpRequest testDurableHttpRequest = new TestDurableHttpRequest( + httpMethod: HttpMethod.Get, + headers: headers, + tokenSource: managedIdentityTokenSource); + + DurableHttpRequest durableHttpRequest = TestOrchestrations.ConvertTestRequestToDurableHttpRequest(testDurableHttpRequest); + string serializedDurableHttpRequest = JsonConvert.SerializeObject(durableHttpRequest); + + Assert.True(JToken.DeepEquals(JObject.Parse(expectedDurableHttpRequestJson), JObject.Parse(serializedDurableHttpRequest))); + } + + [Fact] + [Trait("Category", PlatformSpecificHelpers.TestCategory)] + public void DeserializeManagedIdentityOptions() + { + // Part 1: Check if ManagedIdentityOptions is correctly serialized with TestDurableHttpRequest + Dictionary headers = new Dictionary(); + headers.Add("Accept", "application/json"); + + ManagedIdentityOptions options = new ManagedIdentityOptions(); + options.AuthorityHost = new Uri("https://dummy.login.microsoftonline.com/"); + options.TenantId = "tenant_id"; + + MockTokenSource mockTokenSource = new MockTokenSource("dummy token", options); + + TestDurableHttpRequest request = new TestDurableHttpRequest( + httpMethod: HttpMethod.Get, + headers: headers, + tokenSource: mockTokenSource); + + string serializedTestDurableHttpRequest = JsonConvert.SerializeObject(request); + TestDurableHttpRequest deserializedTestDurableHttpRequest = JsonConvert.DeserializeObject(serializedTestDurableHttpRequest); + + MockTokenSource deserializedMockTokenSource = deserializedTestDurableHttpRequest.TokenSource as MockTokenSource; + Assert.Equal("https://dummy.login.microsoftonline.com/", deserializedMockTokenSource.GetOptions().AuthorityHost.ToString()); + Assert.Equal("tenant_id", deserializedMockTokenSource.GetOptions().TenantId); + + // Part 2: Check if ManagedIdentityOptions is correctly serialized with DurableHttpRequest + ManagedIdentityTokenSource managedIdentityTokenSource = new ManagedIdentityTokenSource("dummy url", options); + TestDurableHttpRequest testDurableHttpRequest = new TestDurableHttpRequest( + httpMethod: HttpMethod.Get, + headers: headers, + tokenSource: managedIdentityTokenSource); + + DurableHttpRequest durableHttpRequest = TestOrchestrations.ConvertTestRequestToDurableHttpRequest(testDurableHttpRequest); + string serializedDurableHttpRequest = JsonConvert.SerializeObject(durableHttpRequest); + DurableHttpRequest deserializedDurableHttpRequest = JsonConvert.DeserializeObject(serializedDurableHttpRequest); + + ManagedIdentityTokenSource deserializedManagedIdentityTokenSource = deserializedDurableHttpRequest.TokenSource as ManagedIdentityTokenSource; + Assert.Equal("https://dummy.login.microsoftonline.com/", deserializedManagedIdentityTokenSource.Options.AuthorityHost.ToString()); + Assert.Equal("tenant_id", deserializedManagedIdentityTokenSource.Options.TenantId); + } + /// /// End-to-end test which checks if the CallHttpAsync Orchestrator returns an OK (200) status code. /// @@ -928,6 +1080,97 @@ public async Task DurableHttpAsync_Synchronous_AddsBearerToken(string storagePro } } + /// + /// End-to-end test which checks if the CallHttpAsync Orchestrator returns an OK (200) status code + /// when the MockTokenSource object takes in a ManagedIdentityOptions object and + /// a Bearer Token is added to the DurableHttpRequest object. + /// + [Theory] + [Trait("Category", PlatformSpecificHelpers.TestCategory)] + [MemberData(nameof(TestDataGenerator.GetFullFeaturedStorageProviderOptions), MemberType = typeof(TestDataGenerator))] + public async Task DurableHttpAsync_Synchronous_TokenWithOptions(string storageProvider) + { + HttpMessageHandler httpMessageHandler = MockSynchronousHttpMessageHandlerForTestingTokenSource(); + + using (ITestHost host = TestHelpers.GetJobHost( + this.loggerProvider, + nameof(this.DurableHttpAsync_Synchronous_TokenWithOptions), + enableExtendedSessions: false, + storageProviderType: storageProvider, + durableHttpMessageHandler: new DurableHttpMessageHandlerFactory(httpMessageHandler))) + { + await host.StartAsync(); + + ManagedIdentityOptions credentialOptions = new ManagedIdentityOptions(); + credentialOptions.AuthorityHost = new Uri("https://dummy.login.microsoftonline.com/"); + credentialOptions.TenantId = "tenant_id"; + + Dictionary headers = new Dictionary(); + headers.Add("Accept", "application/json"); + MockTokenSource mockTokenSource = new MockTokenSource("dummy test token", credentialOptions); + + TestDurableHttpRequest testRequest = new TestDurableHttpRequest( + httpMethod: HttpMethod.Get, + headers: headers, + tokenSource: mockTokenSource); + + var client = await host.StartOrchestratorAsync(nameof(TestOrchestrations.CallHttpAsyncOrchestrator), testRequest, this.output); + var status = await client.WaitForCompletionAsync(this.output, timeout: TimeSpan.FromSeconds(Debugger.IsAttached ? 3000 : 90)); + var output = status?.Output; + DurableHttpResponse response = output.ToObject(); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + await host.StopAsync(); + } + } + + /// + /// End-to-end test which checks if the CallHttpAsync Orchestrator returns an OK (200) status code + /// when the MockTokenSource object takes in a ManagedIdentityOptions object, + /// a Bearer Token is added to the DurableHttpRequest object, and follows the + /// asynchronous pattern. + /// + [Theory] + [Trait("Category", PlatformSpecificHelpers.TestCategory)] + [MemberData(nameof(TestDataGenerator.GetFullFeaturedStorageProviderOptions), MemberType = typeof(TestDataGenerator))] + public async Task DurableHttpAsync_Asynchronous_TokenWithOptions(string storageProvider) + { + HttpMessageHandler httpMessageHandler = MockAsynchronousHttpMessageHandlerForTestingTokenSource(); + + using (ITestHost host = TestHelpers.GetJobHost( + this.loggerProvider, + nameof(this.DurableHttpAsync_Asynchronous_TokenWithOptions), + enableExtendedSessions: false, + storageProviderType: storageProvider, + durableHttpMessageHandler: new DurableHttpMessageHandlerFactory(httpMessageHandler))) + { + await host.StartAsync(); + + ManagedIdentityOptions credentialOptions = new ManagedIdentityOptions(); + credentialOptions.AuthorityHost = new Uri("https://dummy.login.microsoftonline.com/"); + credentialOptions.TenantId = "tenant_id"; + + Dictionary headers = new Dictionary(); + headers.Add("Accept", "application/json"); + MockTokenSource mockTokenSource = new MockTokenSource("dummy test token", credentialOptions); + + TestDurableHttpRequest testRequest = new TestDurableHttpRequest( + httpMethod: HttpMethod.Get, + headers: headers, + tokenSource: mockTokenSource); + + var client = await host.StartOrchestratorAsync(nameof(TestOrchestrations.CallHttpAsyncOrchestrator), testRequest, this.output); + var status = await client.WaitForCompletionAsync(this.output, timeout: TimeSpan.FromSeconds(Debugger.IsAttached ? 3000 : 90)); + var output = status?.Output; + DurableHttpResponse response = output.ToObject(); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + await host.StopAsync(); + } + } + /// /// End-to-end test which checks if the CallHttpAsync Orchestrator returns an OK (200) status code /// when a Bearer Token is added to the DurableHttpRequest object. @@ -1009,7 +1252,7 @@ public async Task DurableHttpAsync_Asynchronous_AddsBearerToken(string storagePr using (ITestHost host = TestHelpers.GetJobHost( this.loggerProvider, - nameof(this.DurableHttpAsync_AsynchronousAPI_ReturnsOK200), + nameof(this.DurableHttpAsync_Asynchronous_AddsBearerToken), enableExtendedSessions: false, storageProviderType: storageProvider, durableHttpMessageHandler: new DurableHttpMessageHandlerFactory(httpMessageHandler))) @@ -1348,16 +1591,25 @@ private static HttpResponseMessage CreateTestHttpResponseMessageMultHeaders( private class MockTokenSource : ITokenSource { [DataMember] - private readonly string token; + private readonly string testToken; + + [DataMember] + private readonly ManagedIdentityOptions options; - public MockTokenSource(string token) + public MockTokenSource(string token, ManagedIdentityOptions options = null) { - this.token = token; + this.testToken = token; + this.options = options; } public Task GetTokenAsync() { - return Task.FromResult(this.token); + return Task.FromResult(this.testToken); + } + + public ManagedIdentityOptions GetOptions() + { + return this.options; } } } diff --git a/test/Common/DurableTaskEndToEndTests.cs b/test/Common/DurableTaskEndToEndTests.cs index 3073fcad9..d4406842f 100644 --- a/test/Common/DurableTaskEndToEndTests.cs +++ b/test/Common/DurableTaskEndToEndTests.cs @@ -1566,7 +1566,8 @@ public async Task UnhandledOrchestrationExceptionWithRetry(bool extendedSessions // Strip '\r' characters to make Windows and Unix output identical. string output = status.Output.ToString().Replace("\r", string.Empty); this.output.WriteLine($"Orchestration output string: {output}"); - Assert.StartsWith($"Orchestrator function '{orchestratorFunctionNames[0]}' failed: The orchestrator function 'ThrowOrchestrator' failed: \"Value cannot be null.\nParameter name: message\"", output); + Assert.StartsWith($"Orchestrator function '{orchestratorFunctionNames[0]}' failed: The orchestrator function 'ThrowOrchestrator' failed: \"Value cannot be null.", output); + Assert.Contains("message", output); string subOrchestrationInstanceId = (string)status.CustomStatus; Assert.NotNull(subOrchestrationInstanceId); @@ -1615,8 +1616,9 @@ public async Task OrchestrationWithRetry_NullRetryOptions(string storageProvider string output = status.Output.ToString().Replace(Environment.NewLine, " "); this.output.WriteLine($"Orchestration output string: {output}"); Assert.StartsWith( - $"Orchestrator function '{orchestratorFunctionNames[0]}' failed: Value cannot be null. Parameter name: retryOptions", + $"Orchestrator function '{orchestratorFunctionNames[0]}' failed: Value cannot be null.", output); + Assert.Contains("retryOptions", output); await host.StopAsync(); } @@ -1800,7 +1802,7 @@ public async Task ActivityWithRetry_NullRetryOptions(string storageProvider) this.output.WriteLine($"Orchestration output string: {output}"); Assert.Contains(orchestratorFunctionName, output); Assert.Contains("Value cannot be null.", output); - Assert.Contains("Parameter name: retryOptions", output); + Assert.Contains("retryOptions", output); await host.StopAsync(); } @@ -1986,33 +1988,6 @@ public async Task Orchestration_OnValidOrchestrator(bool extendedSessions, strin } } - [Theory] - [Trait("Category", PlatformSpecificHelpers.TestCategory)] - [MemberData(nameof(TestDataGenerator.GetBooleanAndFullFeaturedStorageProviderOptions), MemberType = typeof(TestDataGenerator))] - public async Task ThrowExceptionOnLongTimer(bool extendedSessions, string storageProvider) - { - using (ITestHost host = TestHelpers.GetJobHost( - this.loggerProvider, - nameof(this.ThrowExceptionOnLongTimer), - extendedSessions, - storageProviderType: storageProvider)) - { - await host.StartAsync(); - - // Right now, the limit for timers is 6 days. In the future, we'll extend this and update this test. - // https://github.com/Azure/azure-functions-durable-extension/issues/14 - DateTime fireAt = DateTime.UtcNow.AddDays(7); - var client = await host.StartOrchestratorAsync(nameof(TestOrchestrations.Timer), fireAt, this.output); - var status = await client.WaitForCompletionAsync(this.output); - - Assert.NotNull(status); - Assert.Equal(OrchestrationRuntimeStatus.Failed, status.RuntimeStatus); - Assert.Contains("fireAt", status.Output.ToString()); - - await host.StopAsync(); - } - } - [Theory] [Trait("Category", PlatformSpecificHelpers.TestCategory)] [MemberData(nameof(TestDataGenerator.GetBooleanAndFullFeaturedStorageProviderOptions), MemberType = typeof(TestDataGenerator))] @@ -3270,31 +3245,6 @@ public async Task DurableEntity_EntityNameCaseInsensitivity(bool extendedSession } } - [Fact] - [Trait("Category", PlatformSpecificHelpers.TestCategory)] - public async Task AzureStorage_TimerLimitExceeded_ThrowsException() - { - string orchestrationFunctionName = nameof(TestOrchestrations.SimpleTimerSucceeds); - - using (var host = TestHelpers.GetJobHost( - this.loggerProvider, - nameof(this.AzureStorage_TimerLimitExceeded_ThrowsException), - false)) - { - await host.StartAsync(); - - var invalidFireAtTime = DateTime.UtcNow.AddDays(7); - - var client = await host.StartOrchestratorAsync(orchestrationFunctionName, invalidFireAtTime, this.output); - - var status = await client.WaitForCompletionAsync(this.output); - Assert.Equal(OrchestrationRuntimeStatus.Failed, status.RuntimeStatus); - - string output = status.Output.ToString(); - Assert.Contains("fireAt", output); - } - } - [Fact] [Trait("Category", PlatformSpecificHelpers.TestCategory)] public async Task AzureStorage_FirstRetryIntervalLimitHit_ThrowsException() @@ -4461,6 +4411,34 @@ public async Task CustomIMessageSerializerSettingsFactory() } } + [Fact] + [Trait("Category", PlatformSpecificHelpers.TestCategory)] + public async Task CustomSerializerSettings_TypeNameHandlingAll() + { + string[] orchestratorFunctionNames = + { + nameof(TestOrchestrations.SayHelloWithActivity), + }; + + using (var host = TestHelpers.GetJobHost( + this.loggerProvider, + nameof(this.CustomIMessageSerializerSettingsFactory), + true, + serializerSettings: new CustomTypeNameHandlingSettings())) + { + await host.StartAsync(); + + var client = await host.StartOrchestratorAsync(orchestratorFunctionNames[0], "World", this.output); + var status = await client.WaitForCompletionAsync(this.output); + + Assert.Equal(OrchestrationRuntimeStatus.Completed, status?.RuntimeStatus); + Assert.Equal("World", status?.Input); + Assert.Equal("Hello, World!", status?.Output); + + await host.StopAsync(); + } + } + [Fact] [Trait("Category", PlatformSpecificHelpers.TestCategory)] public async Task DefaultIMessageSerializerSettingsFactory() @@ -4743,5 +4721,17 @@ public JsonSerializerSettings CreateJsonSerializerSettings() return serializer; } } + + // JsonSerializerSettings with TypeNameHandling.All + private class CustomTypeNameHandlingSettings : IMessageSerializerSettingsFactory + { + public JsonSerializerSettings CreateJsonSerializerSettings() + { + return new JsonSerializerSettings() + { + TypeNameHandling = TypeNameHandling.All, + }; + } + } } } diff --git a/test/Common/DurableTaskLifeCycleNotificationTest.cs b/test/Common/DurableTaskLifeCycleNotificationTest.cs index 07460d265..68ad88a04 100644 --- a/test/Common/DurableTaskLifeCycleNotificationTest.cs +++ b/test/Common/DurableTaskLifeCycleNotificationTest.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using Microsoft.Azure.WebJobs.Host.TestCommon; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Moq; using Moq.Protected; @@ -1230,7 +1231,11 @@ public void EventGridApiConfigureCheck() wrappedOptions, new LoggerFactory(), mockNameResolver.Object, - new AzureStorageDurabilityProviderFactory(wrappedOptions, connectionStringResolver, mockNameResolver.Object), + new AzureStorageDurabilityProviderFactory( + wrappedOptions, + connectionStringResolver, + mockNameResolver.Object, + NullLoggerFactory.Instance), new TestHostShutdownNotificationService()); var eventGridLifeCycleNotification = (EventGridLifeCycleNotificationHelper)extension.LifeCycleNotificationHelper; @@ -1272,7 +1277,11 @@ public void CustomHelperTypeActivationFailed() wrappedOptions, new LoggerFactory(), nameResolver, - new AzureStorageDurabilityProviderFactory(wrappedOptions, new TestConnectionStringResolver(), nameResolver), + new AzureStorageDurabilityProviderFactory( + wrappedOptions, + new TestConnectionStringResolver(), + nameResolver, + NullLoggerFactory.Instance), new TestHostShutdownNotificationService()); var lifeCycleNotificationHelper = extension.LifeCycleNotificationHelper; @@ -1296,7 +1305,11 @@ public async Task CustomHelperTypeDependencyInjection() wrappedOptions, new LoggerFactory(), nameResolver, - new AzureStorageDurabilityProviderFactory(wrappedOptions, new TestConnectionStringResolver(), nameResolver), + new AzureStorageDurabilityProviderFactory( + wrappedOptions, + new TestConnectionStringResolver(), + nameResolver, + NullLoggerFactory.Instance), new TestHostShutdownNotificationService()); int callCount = 0; diff --git a/test/Common/HttpApiHandlerTests.cs b/test/Common/HttpApiHandlerTests.cs index ce743d71c..d01c9d575 100644 --- a/test/Common/HttpApiHandlerTests.cs +++ b/test/Common/HttpApiHandlerTests.cs @@ -12,6 +12,7 @@ using System.Threading.Tasks; using DurableTask.Core; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Moq; using Newtonsoft.Json; @@ -1177,7 +1178,11 @@ public MockDurableTaskExtension(DurableTaskOptions options) new OptionsWrapper(options), new LoggerFactory(), TestHelpers.GetTestNameResolver(), - new AzureStorageDurabilityProviderFactory(new OptionsWrapper(options), new TestConnectionStringResolver(), TestHelpers.GetTestNameResolver()), + new AzureStorageDurabilityProviderFactory( + new OptionsWrapper(options), + new TestConnectionStringResolver(), + TestHelpers.GetTestNameResolver(), + NullLoggerFactory.Instance), new TestHostShutdownNotificationService(), new DurableHttpMessageHandlerFactory()) { diff --git a/test/Common/TestEntityClasses.cs b/test/Common/TestEntityClasses.cs index 68bf5e6b7..d7fe099ee 100644 --- a/test/Common/TestEntityClasses.cs +++ b/test/Common/TestEntityClasses.cs @@ -142,6 +142,8 @@ public static Task SelfSchedulingEntityFunction([EntityTrigger] IDurableEntityCo return context.DispatchAsync(); } +#pragma warning disable DF0305 // Function named 'ClassBasedFaultyEntity' doesn't have an entity class with the same name defined. Did you mean 'FaultyEntity'? +#pragma warning disable DF0307 // DispatchAsync must be used with the entity name, equal to the name of the function it's used in. [FunctionName("ClassBasedFaultyEntity")] public static Task FaultyEntityFunction([EntityTrigger] IDurableEntityContext context) { @@ -252,6 +254,8 @@ public static Task FaultyEntityFunctionWithoutDispatch([EntityTrigger] IDurableE return Task.CompletedTask; } +#pragma warning restore DF0307 // DispatchAsync must be used with the entity name, equal to the name of the function it's used in. +#pragma warning restore DF0305 //-------------- an entity representing a chat room ----------------- @@ -612,7 +616,7 @@ public class JobWithProxyMultiInterface : IPrimaryJob [JsonProperty("endDate")] public DateTime EndDate { get; private set; } - public void SetId(string Id) => this.Id = Id; + public void SetId(string id) => this.Id = id; public void SetEndDate(DateTime date) => this.EndDate = date; @@ -627,10 +631,6 @@ public void Delete() Entity.Current.DeleteState(); } - public JobWithProxyMultiInterface() - { - } - [FunctionName(nameof(JobWithProxyMultiInterface))] public static Task Run( [EntityTrigger] IDurableEntityContext context) diff --git a/test/Common/TestHelpers.cs b/test/Common/TestHelpers.cs index ab7dc3e16..c77139a47 100644 --- a/test/Common/TestHelpers.cs +++ b/test/Common/TestHelpers.cs @@ -43,6 +43,7 @@ public static ITestHost GetJobHost( TimeSpan? maxQueuePollingInterval = null, string[] eventGridPublishEventTypes = null, string storageProviderType = AzureStorageProviderType, + Type durabilityProviderFactoryType = null, bool autoFetchLargeMessages = true, int httpAsyncSleepTime = 500, IDurableHttpMessageHandlerFactory durableHttpMessageHandler = null, @@ -134,7 +135,8 @@ public static ITestHost GetJobHost( nameResolver, durableHttpMessageHandler, lifeCycleNotificationHelper, - serializerSettings); + serializerSettings, + durabilityProviderFactoryType); } public static ITestHost GetJobHostWithOptions( @@ -144,7 +146,8 @@ public static ITestHost GetJobHostWithOptions( INameResolver nameResolver = null, IDurableHttpMessageHandlerFactory durableHttpMessageHandler = null, ILifeCycleNotificationHelper lifeCycleNotificationHelper = null, - IMessageSerializerSettingsFactory serializerSettings = null) + IMessageSerializerSettingsFactory serializerSettings = null, + Type durabilityProviderFactoryType = null) { if (serializerSettings == null) { @@ -161,6 +164,9 @@ public static ITestHost GetJobHostWithOptions( return PlatformSpecificHelpers.CreateJobHost( optionsWrapper, storageProviderType, +#if !FUNCTIONS_V1 + durabilityProviderFactoryType, +#endif loggerProvider, testNameResolver, durableHttpMessageHandler, diff --git a/test/Common/TestOrchestrations.cs b/test/Common/TestOrchestrations.cs index fd9815617..bfc7484a9 100644 --- a/test/Common/TestOrchestrations.cs +++ b/test/Common/TestOrchestrations.cs @@ -59,7 +59,9 @@ public static async Task AllOrchestratorActivityActions([OrchestrationTr ctx.SignalEntity(input, "count"); ctx.SetCustomStatus("AllAPICallsUsed"); - await ctx.CallHttpAsync(null); + + // next call shouldn't run because MaxOrchestrationCount is reached + await ctx.CallActivityAsync(nameof(TestActivities.Hello), stringInput); return "TestCompleted"; } diff --git a/test/FunctionsV1/PlatformSpecificHelpers.FunctionsV1.cs b/test/FunctionsV1/PlatformSpecificHelpers.FunctionsV1.cs index 97364a37b..355d62f2e 100644 --- a/test/FunctionsV1/PlatformSpecificHelpers.FunctionsV1.cs +++ b/test/FunctionsV1/PlatformSpecificHelpers.FunctionsV1.cs @@ -38,7 +38,11 @@ public static ITestHost CreateJobHost( var loggerFactory = new LoggerFactory(); loggerFactory.AddProvider(loggerProvider); - IDurabilityProviderFactory orchestrationServiceFactory = new AzureStorageDurabilityProviderFactory(options, connectionResolver, nameResolver); + IDurabilityProviderFactory orchestrationServiceFactory = new AzureStorageDurabilityProviderFactory( + options, + connectionResolver, + nameResolver, + loggerFactory); var extension = new DurableTaskExtension( options, diff --git a/test/FunctionsV1/WebJobs.Extensions.DurableTask.Tests.V1.csproj b/test/FunctionsV1/WebJobs.Extensions.DurableTask.Tests.V1.csproj index efff79d3b..fb3a1276a 100644 --- a/test/FunctionsV1/WebJobs.Extensions.DurableTask.Tests.V1.csproj +++ b/test/FunctionsV1/WebJobs.Extensions.DurableTask.Tests.V1.csproj @@ -8,7 +8,7 @@ ..\..\sign.snk - + true true @@ -16,16 +16,15 @@ - + - diff --git a/test/FunctionsV2/AzureStorageDurabilityProviderFactoryTests.cs b/test/FunctionsV2/AzureStorageDurabilityProviderFactoryTests.cs index 9825ec1f7..a5c0df824 100644 --- a/test/FunctionsV2/AzureStorageDurabilityProviderFactoryTests.cs +++ b/test/FunctionsV2/AzureStorageDurabilityProviderFactoryTests.cs @@ -1,25 +1,20 @@ -using Microsoft.Azure.WebJobs; +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using System; +using System.Collections.Generic; +using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.DurableTask; using Microsoft.Azure.WebJobs.Extensions.DurableTask.Tests; +using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Moq; -using System; -using System.Collections.Generic; -using System.Text; using Xunit; -using Xunit.Abstractions; namespace WebJobs.Extensions.DurableTask.Tests.V2 { public class AzureStorageDurabilityProviderFactoryTests { - private readonly ITestOutputHelper output; - - public AzureStorageDurabilityProviderFactoryTests(ITestOutputHelper output) - { - this.output = output; - } - [Fact] [Trait("Category", PlatformSpecificHelpers.TestCategory)] public void DefaultWorkerId_IsMachineName() @@ -27,7 +22,11 @@ public void DefaultWorkerId_IsMachineName() var connectionStringResolver = new TestConnectionStringResolver(); var mockOptions = new OptionsWrapper(new DurableTaskOptions()); var nameResolver = new Mock().Object; - var factory = new AzureStorageDurabilityProviderFactory(mockOptions, connectionStringResolver, nameResolver); + var factory = new AzureStorageDurabilityProviderFactory( + mockOptions, + connectionStringResolver, + nameResolver, + NullLoggerFactory.Instance); var settings = factory.GetAzureStorageOrchestrationServiceSettings(); @@ -36,7 +35,6 @@ public void DefaultWorkerId_IsMachineName() [Fact] [Trait("Category", PlatformSpecificHelpers.TestCategory)] - public void EnvironmentIsVMSS_WorkerIdFromEnvironmentVariables() { var connectionStringResolver = new TestConnectionStringResolver(); @@ -47,7 +45,11 @@ public void EnvironmentIsVMSS_WorkerIdFromEnvironmentVariables() { "RoleInstanceId", "dw0SmallDedicatedWebWorkerRole_hr0HostRole-3-VM-13" }, }); - var factory = new AzureStorageDurabilityProviderFactory(mockOptions, connectionStringResolver, nameResolver); + var factory = new AzureStorageDurabilityProviderFactory( + mockOptions, + connectionStringResolver, + nameResolver, + NullLoggerFactory.Instance); var settings = factory.GetAzureStorageOrchestrationServiceSettings(); diff --git a/test/FunctionsV2/AzureStorageShortenedTimerDurabilityProviderFactory.cs b/test/FunctionsV2/AzureStorageShortenedTimerDurabilityProviderFactory.cs new file mode 100644 index 000000000..0881466a0 --- /dev/null +++ b/test/FunctionsV2/AzureStorageShortenedTimerDurabilityProviderFactory.cs @@ -0,0 +1,37 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using System; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Microsoft.Azure.WebJobs.Extensions.DurableTask.Tests +{ + internal class AzureStorageShortenedTimerDurabilityProviderFactory : AzureStorageDurabilityProviderFactory + { + public AzureStorageShortenedTimerDurabilityProviderFactory( + IOptions options, + IConnectionStringResolver connectionStringResolver, + INameResolver nameResolver, + ILoggerFactory loggerFactory) + : base(options, connectionStringResolver, nameResolver, loggerFactory) + { + } + + public override DurabilityProvider GetDurabilityProvider(DurableClientAttribute attribute) + { + AzureStorageDurabilityProvider provider = base.GetDurabilityProvider(attribute) as AzureStorageDurabilityProvider; + provider.MaximumDelayTime = TimeSpan.FromMinutes(1); + provider.LongRunningTimerIntervalLength = TimeSpan.FromSeconds(25); + return provider; + } + + public override DurabilityProvider GetDurabilityProvider() + { + AzureStorageDurabilityProvider provider = base.GetDurabilityProvider() as AzureStorageDurabilityProvider; + provider.MaximumDelayTime = TimeSpan.FromMinutes(1); + provider.LongRunningTimerIntervalLength = TimeSpan.FromSeconds(25); + return provider; + } + } +} \ No newline at end of file diff --git a/test/FunctionsV2/DurableTaskListenerTests.cs b/test/FunctionsV2/DurableTaskListenerTests.cs index 4d07559ff..63cd83eac 100644 --- a/test/FunctionsV2/DurableTaskListenerTests.cs +++ b/test/FunctionsV2/DurableTaskListenerTests.cs @@ -5,6 +5,7 @@ using Microsoft.Azure.WebJobs.Host.Executors; using Microsoft.Azure.WebJobs.Host.Scale; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Moq; using Xunit; @@ -56,7 +57,11 @@ private static DurableTaskExtension GetDurableTaskConfig() var wrappedOptions = new OptionsWrapper(options); var nameResolver = TestHelpers.GetTestNameResolver(); var connectionStringResolver = new TestConnectionStringResolver(); - var serviceFactory = new AzureStorageDurabilityProviderFactory(wrappedOptions, connectionStringResolver, nameResolver); + var serviceFactory = new AzureStorageDurabilityProviderFactory( + wrappedOptions, + connectionStringResolver, + nameResolver, + NullLoggerFactory.Instance); return new DurableTaskExtension( wrappedOptions, new LoggerFactory(), diff --git a/test/FunctionsV2/OutOfProcTests.cs b/test/FunctionsV2/OutOfProcTests.cs index e546b356d..ba92b70d1 100644 --- a/test/FunctionsV2/OutOfProcTests.cs +++ b/test/FunctionsV2/OutOfProcTests.cs @@ -148,6 +148,63 @@ public async Task CallHttpActionOrchestrationWithManagedIdentity() Assert.Equal("https://management.core.windows.net", tokenSource.Resource); } + [Fact] + [Trait("Category", PlatformSpecificHelpers.TestCategory)] + public async Task CallHttpActionOrchestrationWithManagedIdentityOptions() + { + DurableHttpRequest request = null; + + // Mock the CallHttpAsync API so we can capture the request and return a fixed response. + var contextMock = new Mock(); + contextMock + .Setup(ctx => ctx.CallHttpAsync(It.IsAny())) + .Callback(req => request = req) + .Returns(Task.FromResult(new DurableHttpResponse(System.Net.HttpStatusCode.OK))); + + var shim = new OutOfProcOrchestrationShim(contextMock.Object); + + var executionJson = @" +{ + ""isDone"": false, + ""actions"": [ + [{ + ""actionType"": ""CallHttp"", + ""httpRequest"": { + ""method"": ""GET"", + ""uri"": ""https://example.com"", + ""tokenSource"": { + ""kind"": ""AzureManagedIdentity"", + ""resource"": ""https://management.core.windows.net"", + ""options"": { + ""authorityhost"": ""https://login.microsoftonline.com/"", + ""tenantid"": ""example_tenant_id"" + } + } + } + }] + ] +}"; + + // Feed the out-of-proc execution result JSON to the out-of-proc shim. + var jsonObject = JObject.Parse(executionJson); + OrchestrationInvocationResult result = new OrchestrationInvocationResult() + { + ReturnValue = jsonObject, + }; + bool moreWork = await shim.ScheduleDurableTaskEvents(result); + + Assert.NotNull(request); + Assert.Equal(HttpMethod.Get, request.Method); + Assert.Equal(new Uri("https://example.com"), request.Uri); + Assert.Null(request.Content); + + Assert.NotNull(request.TokenSource); + ManagedIdentityTokenSource tokenSource = Assert.IsType(request.TokenSource); + Assert.Equal("https://management.core.windows.net", tokenSource.Resource); + Assert.Equal("https://login.microsoftonline.com/", tokenSource.Options.AuthorityHost.ToString()); + Assert.Equal("example_tenant_id", tokenSource.Options.TenantId); + } + [Theory] [Trait("Category", PlatformSpecificHelpers.TestCategory)] [InlineData(true)] diff --git a/test/FunctionsV2/PlatformSpecificHelpers.FunctionsV2.cs b/test/FunctionsV2/PlatformSpecificHelpers.FunctionsV2.cs index a849a4ca5..071bea857 100644 --- a/test/FunctionsV2/PlatformSpecificHelpers.FunctionsV2.cs +++ b/test/FunctionsV2/PlatformSpecificHelpers.FunctionsV2.cs @@ -24,6 +24,7 @@ public static class PlatformSpecificHelpers public static ITestHost CreateJobHost( IOptions options, string storageProvider, + Type durabilityProviderFactoryType, ILoggerProvider loggerProvider, INameResolver nameResolver, IDurableHttpMessageHandlerFactory durableHttpMessageHandler, @@ -39,7 +40,7 @@ public static ITestHost CreateJobHost( .ConfigureWebJobs( webJobsBuilder => { - webJobsBuilder.AddDurableTask(options, storageProvider); + webJobsBuilder.AddDurableTask(options, storageProvider, durabilityProviderFactoryType); webJobsBuilder.AddAzureStorage(); }) .ConfigureServices( @@ -65,8 +66,15 @@ public static ITestHost CreateJobHost( return new FunctionsV2HostWrapper(host); } - private static IWebJobsBuilder AddDurableTask(this IWebJobsBuilder builder, IOptions options, string storageProvider) + private static IWebJobsBuilder AddDurableTask(this IWebJobsBuilder builder, IOptions options, string storageProvider, Type durabilityProviderFactoryType = null) { + if (durabilityProviderFactoryType != null) + { + builder.Services.AddSingleton(typeof(IDurabilityProviderFactory), typeof(AzureStorageShortenedTimerDurabilityProviderFactory)); + builder.AddDurableTask(options); + return builder; + } + switch (storageProvider) { case TestHelpers.RedisProviderType: diff --git a/test/FunctionsV2/TimerTests.cs b/test/FunctionsV2/TimerTests.cs new file mode 100644 index 000000000..8c3c1adc6 --- /dev/null +++ b/test/FunctionsV2/TimerTests.cs @@ -0,0 +1,73 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using System; +using System.Threading.Tasks; +using Microsoft.Azure.WebJobs.Extensions.DurableTask; +using Microsoft.Azure.WebJobs.Extensions.DurableTask.Tests; +using Microsoft.Azure.WebJobs.Host.TestCommon; +using Xunit; +using Xunit.Abstractions; + +namespace WebJobs.Extensions.DurableTask.Tests.V2 +{ + public class TimerTests + { + private readonly ITestOutputHelper output; + private readonly TestLoggerProvider loggerProvider; + + public TimerTests(ITestOutputHelper output) + { + this.output = output; + this.loggerProvider = new TestLoggerProvider(output); + } + + [Theory] + [Trait("Category", PlatformSpecificHelpers.TestCategory)] + [InlineData(true)] + [InlineData(false)] + public async Task LongRunningTimer(bool extendedSessions) + { + using (ITestHost host = TestHelpers.GetJobHost( + this.loggerProvider, + nameof(this.LongRunningTimer), + extendedSessions, + storageProviderType: "azure_storage", + durabilityProviderFactoryType: typeof(AzureStorageShortenedTimerDurabilityProviderFactory))) + { + await host.StartAsync(); + + var fireAt = DateTime.UtcNow.AddMinutes(2); + var client = await host.StartOrchestratorAsync(nameof(TestOrchestrations.Timer), fireAt, this.output); + var status = await client.WaitForCompletionAsync(this.output, timeout: TimeSpan.FromMinutes(3)); + + Assert.Equal(OrchestrationRuntimeStatus.Completed, status.RuntimeStatus); + await host.StopAsync(); + } + } + + [Theory] + [Trait("Category", PlatformSpecificHelpers.TestCategory)] + [InlineData(true)] + [InlineData(false)] + public async Task TimerLengthLessThanMaxTime(bool extendedSessions) + { + using (ITestHost host = TestHelpers.GetJobHost( + this.loggerProvider, + nameof(this.LongRunningTimer), + extendedSessions, + storageProviderType: "azure_storage", + durabilityProviderFactoryType: typeof(AzureStorageShortenedTimerDurabilityProviderFactory))) + { + await host.StartAsync(); + + var fireAt = DateTime.UtcNow.AddSeconds(30); + var client = await host.StartOrchestratorAsync(nameof(TestOrchestrations.Timer), fireAt, this.output); + var status = await client.WaitForCompletionAsync(this.output, timeout: TimeSpan.FromMinutes(2)); + + Assert.Equal(OrchestrationRuntimeStatus.Completed, status.RuntimeStatus); + await host.StopAsync(); + } + } + } +} diff --git a/test/FunctionsV2/WebJobs.Extensions.DurableTask.Tests.V2.csproj b/test/FunctionsV2/WebJobs.Extensions.DurableTask.Tests.V2.csproj index 5eeea4808..b4e299bf5 100644 --- a/test/FunctionsV2/WebJobs.Extensions.DurableTask.Tests.V2.csproj +++ b/test/FunctionsV2/WebJobs.Extensions.DurableTask.Tests.V2.csproj @@ -1,14 +1,14 @@  - netcoreapp2.0 + netcoreapp3.1 Microsoft Corporation SA0001;SA1600;SA1615 true ..\..\sign.snk - + false true @@ -16,12 +16,11 @@ - - + diff --git a/test/WebJobs.Extensions.DurableTask.Analyzers.Test/ActivityFunction/ArgumentAnalyzerTests.cs b/test/WebJobs.Extensions.DurableTask.Analyzers.Test/ActivityFunction/ArgumentAnalyzerTests.cs index e552baed2..6ecc9021d 100644 --- a/test/WebJobs.Extensions.DurableTask.Analyzers.Test/ActivityFunction/ArgumentAnalyzerTests.cs +++ b/test/WebJobs.Extensions.DurableTask.Analyzers.Test/ActivityFunction/ArgumentAnalyzerTests.cs @@ -12,7 +12,7 @@ namespace Microsoft.Azure.WebJobs.Extensions.DurableTask.Analyzers.Test.Activity public class ArgumentAnalyzerTests : CodeFixVerifier { private static readonly string DiagnosticId = ArgumentAnalyzer.DiagnosticId; - private static readonly DiagnosticSeverity Severity = ArgumentAnalyzer.Severity; + private static readonly DiagnosticSeverity Severity = DiagnosticSeverity.Warning; [TestMethod] public void Argument_NoDiagnosticTestCases() @@ -94,6 +94,10 @@ public static async Task Run( await context.CallActivityAsync(""Test_UnusedInputFromContext"", null); return ""Hello World!""; + + // Nightmare Test; testing nested Tuples, JSON compatible types, indirect subclass, getting input on a context + + await context.CallActivityAsync(""Test_NightmareTest"", new List), Decimal>>()); } // Functions Testing different matching input types @@ -204,6 +208,15 @@ public static string TestUnusedInputFromContext([ActivityTrigger] IDurableActivi { return $""Hello {name}!""; } + + // Nightmare Test; testing nested Tuples, JSON compatible types, indirect subclass, getting input on a context + + [FunctionName(""Test_NightmareTest"")] + public static string TestNightmareTest([ActivityTrigger] IDurableActivityContext context) + { + string name = context.GetInput), Object>[]>(); + return $""Hello World!""; + } } }"; VerifyCSharpDiagnostic(test); @@ -345,7 +358,7 @@ public static string FunctionTakesString([ActivityTrigger] string name) var expectedDiagnostics = new DiagnosticResult { Id = DiagnosticId, - Message = string.Format(Resources.ActivityArgumentAnalyzerMessageFormat, "Function_Takes_String", "string", "System.String[]"), + Message = string.Format(Resources.ActivityArgumentAnalyzerMessageFormat, "Function_Takes_String", "string", "string[]"), Severity = Severity, Locations = new[] { diff --git a/test/WebJobs.Extensions.DurableTask.Analyzers.Test/ActivityFunction/FunctionReturnTypeAnalyzerTests.cs b/test/WebJobs.Extensions.DurableTask.Analyzers.Test/ActivityFunction/FunctionReturnTypeAnalyzerTests.cs index 0894000f4..1ed5e7ddf 100644 --- a/test/WebJobs.Extensions.DurableTask.Analyzers.Test/ActivityFunction/FunctionReturnTypeAnalyzerTests.cs +++ b/test/WebJobs.Extensions.DurableTask.Analyzers.Test/ActivityFunction/FunctionReturnTypeAnalyzerTests.cs @@ -12,7 +12,7 @@ namespace Microsoft.Azure.WebJobs.Extensions.DurableTask.Analyzers.Test.Activity public class FunctionReturnTypeAnalyzerTests : CodeFixVerifier { private static readonly string DiagnosticId = FunctionReturnTypeAnalyzer.DiagnosticId; - private static readonly DiagnosticSeverity Severity = FunctionReturnTypeAnalyzer.Severity; + private static readonly DiagnosticSeverity Severity = DiagnosticSeverity.Warning; [TestMethod] public void ReturnType_NoDiagnosticTestCases() diff --git a/test/WebJobs.Extensions.DurableTask.Analyzers.Test/ActivityFunction/NameAnalyzerTests.cs b/test/WebJobs.Extensions.DurableTask.Analyzers.Test/ActivityFunction/NameAnalyzerTests.cs index 8e61b2370..0768c21f0 100644 --- a/test/WebJobs.Extensions.DurableTask.Analyzers.Test/ActivityFunction/NameAnalyzerTests.cs +++ b/test/WebJobs.Extensions.DurableTask.Analyzers.Test/ActivityFunction/NameAnalyzerTests.cs @@ -13,7 +13,7 @@ namespace Microsoft.Azure.WebJobs.Extensions.DurableTask.Analyzers.Test.Activity public class NameAnalyzerTests : CodeFixVerifier { private static readonly string DiagnosticId = NameAnalyzer.DiagnosticId; - private static readonly DiagnosticSeverity Severity = NameAnalyzer.Severity; + private static readonly DiagnosticSeverity Severity = DiagnosticSeverity.Warning; [TestMethod] public void Name_NoDiagnosticTestCases() @@ -37,6 +37,7 @@ public static class HelloSequence // For test cases below public const string TestFunctionUsesConstant = ""TestFunctionNameWithConstant""; public const string TestFunctionNameWithClass = ""TestFunctionNameWithClass""; + public const string TestFunctionInDependencies = ""TestFunctionInDependencies""; // Testing that no diagnostics are produced when method does not have the FunctionName attribute present public static async Task NonFunctionInvalidNames( @@ -55,31 +56,35 @@ public static async Task Run( { // Matching names (strings) - await context.CallActivityAsync(""Test_MatchingStrings"", ""Tokyo""); + await context.CallActivityAsync(""Test_MatchingStrings"", ""Prairie View""); // Invocation and function using nameof() - await context.CallActivityAsync(nameof(TestFunctionUsesNameOfClassName), ""Amsterdam""); + await context.CallActivityAsync(nameof(TestFunctionUsesNameOfClassName), ""Minneapolis""); // Invocation uses string, function uses nameof() - await context.CallActivityAsync(""TestFunctionUsesNameOfMethodName"", ""Amsterdam""); + await context.CallActivityAsync(""TestFunctionUsesNameOfMethodName"", ""Brunswick""); // Invocation uses string, function uses constant - await context.CallActivityAsync(""TestFunctionNameWithConstant"", ""Amsterdam""); + await context.CallActivityAsync(""TestFunctionNameWithConstant"", ""Minneapolis""); // Invocation uses string, function uses constant prefaced with class name - await context.CallActivityAsync(""TestFunctionNameWithClass"", ""Amsterdam""); + await context.CallActivityAsync(""TestFunctionNameWithClass"", ""Ann Arbor""); // Invocation uses constant, function uses constant - await context.CallActivityAsync(TestFunctionUsesConstant, ""Amsterdam""); + await context.CallActivityAsync(TestFunctionUsesConstant, ""Fort Worth""); // Invocation uses constant prefaced with class name, function uses nameof() - await context.CallActivityAsync(HelloSequence.TestFunctionNameWithClass, ""Amsterdam""); + await context.CallActivityAsync(HelloSequence.TestFunctionNameWithClass, ""Louisville""); + + // Invocation uses constant, function not found in source code, covers true + + await context.CallActivityAsync(TestFunctionInDependencies, ""Minneapolis""); return ""Hello World!""; } diff --git a/test/WebJobs.Extensions.DurableTask.Analyzers.Test/Binding/ClientAnalyzerTests.cs b/test/WebJobs.Extensions.DurableTask.Analyzers.Test/Binding/ClientAnalyzerTests.cs index 2835022a9..71cf16333 100644 --- a/test/WebJobs.Extensions.DurableTask.Analyzers.Test/Binding/ClientAnalyzerTests.cs +++ b/test/WebJobs.Extensions.DurableTask.Analyzers.Test/Binding/ClientAnalyzerTests.cs @@ -13,74 +13,77 @@ namespace Microsoft.Azure.WebJobs.Extensions.DurableTask.Analyzers.Test.Binding public class ClientAnalyzerTests : CodeFixVerifier { private static readonly string DiagnosticId = ClientAnalyzer.DiagnosticId; - private static readonly DiagnosticSeverity Severity = ClientAnalyzer.Severity; + private static readonly DiagnosticSeverity Severity = DiagnosticSeverity.Warning; private const string V1ExpectedFix = @" +using System.Threading.Tasks; +using System.Collections.Generic; using Microsoft.Azure.WebJobs; -using Microsoft.Extensions.Logging; -namespace ExternalInteraction +namespace VSSample { - public static class HireEmployee + public static class HelloSequence { - [FunctionName(""HireEmployee"")] - public static async Task RunOrchestrator( - [OrchestrationClient] DurableOrchestrationClient client, - ILogger log) + [FunctionName(""ClientAnalyzerTestCases"")] + public static async Task Run( + [OrchestrationClient] DurableOrchestrationClient client) { } }"; private const string V2ClientExpectedFix = @" +using System.Threading.Tasks; +using System.Collections.Generic; +using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.DurableTask; -using Microsoft.Extensions.Logging; -namespace ExternalInteraction +namespace VSSample { - public static class HireEmployee + public static class HelloSequence { - [FunctionName(""HireEmployee"")] - public static async Task RunOrchestrator( - [DurableClient] IDurableClient client, - ILogger log) + [FunctionName(""ClientAnalyzerTestCases"")] + public static async Task Run( + [DurableClient] IDurableClient client) { } }"; private const string V2OrchestrationClientExpectedFix = @" +using System.Threading.Tasks; +using System.Collections.Generic; +using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.DurableTask; -using Microsoft.Extensions.Logging; -namespace ExternalInteraction +namespace VSSample { - public static class HireEmployee + public static class HelloSequence { - [FunctionName(""HireEmployee"")] - public static async Task RunOrchestrator( - [DurableClient] IDurableOrchestrationClient client, - ILogger log) + [FunctionName(""ClientAnalyzerTestCases"")] + public static async Task Run( + [DurableClient] IDurableOrchestrationClient client) { } }"; private const string V2EntityClientExpectedFix = @" +using System.Threading.Tasks; +using System.Collections.Generic; +using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.DurableTask; -using Microsoft.Extensions.Logging; -namespace ExternalInteraction +namespace VSSample { - public static class HireEmployee + public static class HelloSequence { - [FunctionName(""HireEmployee"")] - public static async Task RunOrchestrator( - [DurableClient] IDurableEntityClient client, - ILogger log) + [FunctionName(""ClientAnalyzerTestCases"")] + public static async Task Run( + [DurableClient] IDurableEntityClient client) { } }"; [TestMethod] - public void DurableClient_V1_NonIssue() + public void DurableClient_V1_NoDiagnosticTestCases() { SyntaxNodeUtils.version = DurableVersion.V1; @@ -88,7 +91,7 @@ public void DurableClient_V1_NonIssue() } [TestMethod] - public void DurableClient_V2_NonIssue() + public void DurableClient_V2_NoDiagnosticTestCases() { SyntaxNodeUtils.version = DurableVersion.V2; @@ -97,21 +100,22 @@ public void DurableClient_V2_NonIssue() VerifyCSharpDiagnostic(V2EntityClientExpectedFix); } + // Tests SyntaxKind.IdentifierName [TestMethod] - public void OrchestrationClient_V1_Object() + public void OrchestrationClient_V1_UsingObject() { var test = @" +using System.Threading.Tasks; +using System.Collections.Generic; using Microsoft.Azure.WebJobs; -using Microsoft.Extensions.Logging; -namespace ExternalInteraction +namespace VSSample { - public static class HireEmployee + public static class HelloSequence { - [FunctionName(""HireEmployee"")] - public static async Task RunOrchestrator( - [OrchestrationClient] Object client, - ILogger log) + [FunctionName(""ClientAnalyzerTestCases"")] + public static async Task Run( + [OrchestrationClient] Object client) { } }"; @@ -122,7 +126,7 @@ public static async Task RunOrchestrator( Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 11, 35) + new DiagnosticResultLocation("Test0.cs", 12, 35) } }; @@ -133,21 +137,22 @@ public static async Task RunOrchestrator( VerifyCSharpFix(test, V1ExpectedFix); } + // Tests SyntaxKind.PredefinedType [TestMethod] - public void OrchestrationClient_V1_String() + public void OrchestrationClient_V1_UsingString() { var test = @" +using System.Threading.Tasks; +using System.Collections.Generic; using Microsoft.Azure.WebJobs; -using Microsoft.Extensions.Logging; -namespace ExternalInteraction +namespace VSSample { - public static class HireEmployee + public static class HelloSequence { - [FunctionName(""HireEmployee"")] - public static async Task RunOrchestrator( - [OrchestrationClient] string client, - ILogger log) + [FunctionName(""ClientAnalyzerTestCases"")] + public static async Task Run( + [OrchestrationClient] string client) { } }"; @@ -158,7 +163,7 @@ public static async Task RunOrchestrator( Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 11, 35) + new DiagnosticResultLocation("Test0.cs", 12, 35) } }; @@ -169,32 +174,33 @@ public static async Task RunOrchestrator( VerifyCSharpFix(test, V1ExpectedFix, allowNewCompilerDiagnostics: true); } + // Tests SyntaxKind.GenericName [TestMethod] - public void OrchestrationClient_V1_V2DurableInterface() + public void OrchestrationClient_V1_UsingList() { var test = @" +using System.Threading.Tasks; +using System.Collections.Generic; using Microsoft.Azure.WebJobs; -using Microsoft.Extensions.Logging; -namespace ExternalInteraction +namespace VSSample { - public static class HireEmployee + public static class HelloSequence { - [FunctionName(""HireEmployee"")] - public static async Task RunOrchestrator( - [OrchestrationClient] IDurableClient client, - ILogger log) + [FunctionName(""ClientAnalyzerTestCases"")] + public static async Task Run( + [OrchestrationClient] List client) { } }"; var expectedDiagnostics = new DiagnosticResult { Id = DiagnosticId, - Message = string.Format(Resources.V1ClientAnalyzerMessageFormat, "IDurableClient"), + Message = string.Format(Resources.V1ClientAnalyzerMessageFormat, "List"), Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 11, 35) + new DiagnosticResultLocation("Test0.cs", 12, 35) } }; @@ -202,35 +208,110 @@ public static async Task RunOrchestrator( VerifyCSharpDiagnostic(test, expectedDiagnostics); - VerifyCSharpFix(test, V1ExpectedFix); + VerifyCSharpFix(test, V1ExpectedFix, allowNewCompilerDiagnostics: true); + } + + // Tests SyntaxKind.ArrayType + [TestMethod] + public void OrchestrationClient_V1_UsingArray() + { + var test = @" +using System.Threading.Tasks; +using System.Collections.Generic; +using Microsoft.Azure.WebJobs; + +namespace VSSample +{ + public static class HelloSequence + { + [FunctionName(""ClientAnalyzerTestCases"")] + public static async Task Run( + [OrchestrationClient] string[] client) + { + } +}"; + var expectedDiagnostics = new DiagnosticResult + { + Id = DiagnosticId, + Message = string.Format(Resources.V1ClientAnalyzerMessageFormat, "string[]"), + Severity = Severity, + Locations = + new[] { + new DiagnosticResultLocation("Test0.cs", 12, 35) + } + }; + + SyntaxNodeUtils.version = DurableVersion.V1; + + VerifyCSharpDiagnostic(test, expectedDiagnostics); + + VerifyCSharpFix(test, V1ExpectedFix, allowNewCompilerDiagnostics: true); } + // Tests SyntaxKind.TupleType [TestMethod] - public void OrchestrationClient_V1_Tuple() + public void OrchestrationClient_V1_UsingValueTuple() { var test = @" +using System.Threading.Tasks; +using System.Collections.Generic; using Microsoft.Azure.WebJobs; -using Microsoft.Extensions.Logging; -namespace ExternalInteraction +namespace VSSample { - public static class HireEmployee + public static class HelloSequence { - [FunctionName(""HireEmployee"")] - public static async Task RunOrchestrator( - [OrchestrationClient] Tuple client, - ILogger log) + [FunctionName(""ClientAnalyzerTestCases"")] + public static async Task Run( + [OrchestrationClient] (string, int) client) { } }"; var expectedDiagnostics = new DiagnosticResult { Id = DiagnosticId, - Message = string.Format(Resources.V1ClientAnalyzerMessageFormat, "Tuple"), + Message = string.Format(Resources.V1ClientAnalyzerMessageFormat, "(string, int)"), Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 11, 35) + new DiagnosticResultLocation("Test0.cs", 12, 35) + } + }; + + SyntaxNodeUtils.version = DurableVersion.V1; + + VerifyCSharpDiagnostic(test, expectedDiagnostics); + + VerifyCSharpFix(test, V1ExpectedFix, allowNewCompilerDiagnostics: true); + } + + // Tests Durable V2 Client + [TestMethod] + public void OrchestrationClient_V1_UsingV2Client() + { + var test = @" +using System.Threading.Tasks; +using System.Collections.Generic; +using Microsoft.Azure.WebJobs; + +namespace VSSample +{ + public static class HelloSequence + { + [FunctionName(""ClientAnalyzerTestCases"")] + public static async Task Run( + [OrchestrationClient] IDurableClient client) + { + } +}"; + var expectedDiagnostics = new DiagnosticResult + { + Id = DiagnosticId, + Message = string.Format(Resources.V1ClientAnalyzerMessageFormat, "IDurableClient"), + Severity = Severity, + Locations = + new[] { + new DiagnosticResultLocation("Test0.cs", 12, 35) } }; @@ -241,21 +322,23 @@ public static async Task RunOrchestrator( VerifyCSharpFix(test, V1ExpectedFix); } + // Tests SyntaxKind.IdentifierName [TestMethod] - public void DurableClient_V2_Object() + public void DurableClient_V2_UsingObject() { var test = @" +using System.Threading.Tasks; +using System.Collections.Generic; +using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.DurableTask; -using Microsoft.Extensions.Logging; -namespace ExternalInteraction +namespace VSSample { - public static class HireEmployee + public static class HelloSequence { - [FunctionName(""HireEmployee"")] - public static async Task RunOrchestrator( - [DurableClient] Object client, - ILogger log) + [FunctionName(""ClientAnalyzerTestCases"")] + public static async Task Run( + [DurableClient] Object client) { } }"; @@ -266,7 +349,7 @@ public static async Task RunOrchestrator( Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 11, 29) + new DiagnosticResultLocation("Test0.cs", 13, 29) } }; @@ -279,21 +362,23 @@ public static async Task RunOrchestrator( VerifyCSharpFix(test, V2OrchestrationClientExpectedFix, 2); } + // Tests SyntaxKind.PredefinedType [TestMethod] - public void DurableClient_V2_String() + public void DurableClient_V2_UsingString() { var test = @" +using System.Threading.Tasks; +using System.Collections.Generic; +using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.DurableTask; -using Microsoft.Extensions.Logging; -namespace ExternalInteraction +namespace VSSample { - public static class HireEmployee + public static class HelloSequence { - [FunctionName(""HireEmployee"")] - public static async Task RunOrchestrator( - [DurableClient] string client, - ILogger log) + [FunctionName(""ClientAnalyzerTestCases"")] + public static async Task Run( + [DurableClient] string client) { } }"; @@ -304,7 +389,7 @@ public static async Task RunOrchestrator( Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 11, 29) + new DiagnosticResultLocation("Test0.cs", 13, 29) } }; @@ -317,32 +402,114 @@ public static async Task RunOrchestrator( VerifyCSharpFix(test, V2OrchestrationClientExpectedFix, 2); } + // Tests SyntaxKind.GenericName [TestMethod] - public void DurableClient_V2_V1DurableClass() + public void DurableClient_V2_UsingList() { var test = @" +using System.Threading.Tasks; +using System.Collections.Generic; +using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.DurableTask; -using Microsoft.Extensions.Logging; -namespace ExternalInteraction +namespace VSSample { - public static class HireEmployee + public static class HelloSequence { - [FunctionName(""HireEmployee"")] - public static async Task RunOrchestrator( - [DurableClient] DurableOrchestrationClient client, - ILogger log) + [FunctionName(""ClientAnalyzerTestCases"")] + public static async Task Run( + [DurableClient] List client) { } }"; var expectedDiagnostics = new DiagnosticResult { Id = DiagnosticId, - Message = string.Format(Resources.V2ClientAnalyzerMessageFormat, "DurableOrchestrationClient"), + Message = string.Format(Resources.V2ClientAnalyzerMessageFormat, "List"), + Severity = Severity, + Locations = + new[] { + new DiagnosticResultLocation("Test0.cs", 13, 29) + } + }; + + SyntaxNodeUtils.version = DurableVersion.V2; + + VerifyCSharpDiagnostic(test, expectedDiagnostics); + + VerifyCSharpFix(test, V2ClientExpectedFix, 0); + VerifyCSharpFix(test, V2EntityClientExpectedFix, 1); + VerifyCSharpFix(test, V2OrchestrationClientExpectedFix, 2); + } + + // Tests SyntaxKind.ArrayType + [TestMethod] + public void DurableClient_V2_UsingArray() + { + var test = @" +using System.Threading.Tasks; +using System.Collections.Generic; +using Microsoft.Azure.WebJobs; +using Microsoft.Azure.WebJobs.Extensions.DurableTask; + +namespace VSSample +{ + public static class HelloSequence + { + [FunctionName(""ClientAnalyzerTestCases"")] + public static async Task Run( + [DurableClient] string[] client) + { + } +}"; + var expectedDiagnostics = new DiagnosticResult + { + Id = DiagnosticId, + Message = string.Format(Resources.V2ClientAnalyzerMessageFormat, "string[]"), + Severity = Severity, + Locations = + new[] { + new DiagnosticResultLocation("Test0.cs", 13, 29) + } + }; + + SyntaxNodeUtils.version = DurableVersion.V2; + + VerifyCSharpDiagnostic(test, expectedDiagnostics); + + VerifyCSharpFix(test, V2ClientExpectedFix, 0); + VerifyCSharpFix(test, V2EntityClientExpectedFix, 1); + VerifyCSharpFix(test, V2OrchestrationClientExpectedFix, 2); + } + + // Tests SyntaxKind.TupleType + [TestMethod] + public void DurableClient_V2_UsingValueTuple() + { + var test = @" +using System.Threading.Tasks; +using System.Collections.Generic; +using Microsoft.Azure.WebJobs; +using Microsoft.Azure.WebJobs.Extensions.DurableTask; + +namespace VSSample +{ + public static class HelloSequence + { + [FunctionName(""ClientAnalyzerTestCases"")] + public static async Task Run( + [DurableClient] (string, int) client) + { + } +}"; + var expectedDiagnostics = new DiagnosticResult + { + Id = DiagnosticId, + Message = string.Format(Resources.V2ClientAnalyzerMessageFormat, "(string, int)"), Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 11, 29) + new DiagnosticResultLocation("Test0.cs", 13, 29) } }; @@ -355,32 +522,34 @@ public static async Task RunOrchestrator( VerifyCSharpFix(test, V2OrchestrationClientExpectedFix, 2); } + // Tests Durable V1 Client [TestMethod] - public void DurableClient_V2_Tuple() + public void DurableClient_V2_V1DurableClass() { var test = @" +using System.Threading.Tasks; +using System.Collections.Generic; +using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.DurableTask; -using Microsoft.Extensions.Logging; -namespace ExternalInteraction +namespace VSSample { - public static class HireEmployee + public static class HelloSequence { - [FunctionName(""HireEmployee"")] - public static async Task RunOrchestrator( - [DurableClient] Tuple client, - ILogger log) + [FunctionName(""ClientAnalyzerTestCases"")] + public static async Task Run( + [DurableClient] DurableOrchestrationClient client) { } }"; var expectedDiagnostics = new DiagnosticResult { Id = DiagnosticId, - Message = string.Format(Resources.V2ClientAnalyzerMessageFormat, "Tuple"), + Message = string.Format(Resources.V2ClientAnalyzerMessageFormat, "DurableOrchestrationClient"), Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 11, 29) + new DiagnosticResultLocation("Test0.cs", 13, 29) } }; diff --git a/test/WebJobs.Extensions.DurableTask.Analyzers.Test/Binding/EntityContentAnalyzerTests.cs b/test/WebJobs.Extensions.DurableTask.Analyzers.Test/Binding/EntityContentAnalyzerTests.cs index c28f24ea7..afd6abe88 100644 --- a/test/WebJobs.Extensions.DurableTask.Analyzers.Test/Binding/EntityContentAnalyzerTests.cs +++ b/test/WebJobs.Extensions.DurableTask.Analyzers.Test/Binding/EntityContentAnalyzerTests.cs @@ -13,46 +13,49 @@ namespace Microsoft.Azure.WebJobs.Extensions.DurableTask.Analyzers.Test.Binding public class EntityContentAnalyzerTests : CodeFixVerifier { private static readonly string DiagnosticId = EntityContextAnalyzer.DiagnosticId; - private static readonly DiagnosticSeverity Severity = EntityContextAnalyzer.Severity; + private static readonly DiagnosticSeverity Severity = DiagnosticSeverity.Warning; private const string ExpectedFix = @" +using System.Threading.Tasks; +using System.Collections.Generic; +using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.DurableTask; -using Microsoft.Extensions.Logging; -namespace ExternalInteraction +namespace VSSample { - public static class HireEmployee + public static class HelloSequence { - [FunctionName(""HireEmployee"")] - public static async Task RunOrchestrator( - [EntityTrigger] IDurableEntityContext context, - ILogger log) + [FunctionName(""EntityContextAnalyzerTestCases"")] + public static async Task Run( + [EntityTrigger] IDurableEntityContext context) { } } }"; [TestMethod] - public void EntityTrigger_NonIssue() + public void EntityTrigger_NoDiagnosticTestCases() { VerifyCSharpDiagnostic(ExpectedFix); } + // Tests SyntaxKind.IdentifierName [TestMethod] - public void EntityTrigger_Object() + public void EntityTrigger_UsingObject() { var test = @" +using System.Threading.Tasks; +using System.Collections.Generic; +using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.DurableTask; -using Microsoft.Extensions.Logging; -namespace ExternalInteraction +namespace VSSample { - public static class HireEmployee + public static class HelloSequence { - [FunctionName(""HireEmployee"")] - public static async Task RunOrchestrator( - [EntityTrigger] Object context, - ILogger log) + [FunctionName(""EntityContextAnalyzerTestCases"")] + public static async Task Run( + [EntityTrigger] Object context) { } } @@ -64,7 +67,7 @@ public static async Task RunOrchestrator( Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 11, 29) + new DiagnosticResultLocation("Test0.cs", 13, 29) } }; @@ -73,21 +76,23 @@ public static async Task RunOrchestrator( VerifyCSharpFix(test, ExpectedFix); } + // Tests SyntaxKind.PredefinedType [TestMethod] - public void EntityTrigger_String() + public void EntityTrigger_UsingString() { var test = @" +using System.Threading.Tasks; +using System.Collections.Generic; +using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.DurableTask; -using Microsoft.Extensions.Logging; -namespace ExternalInteraction +namespace VSSample { - public static class HireEmployee + public static class HelloSequence { - [FunctionName(""HireEmployee"")] - public static async Task RunOrchestrator( - [EntityTrigger] string context, - ILogger log) + [FunctionName(""EntityContextAnalyzerTestCases"")] + public static async Task Run( + [EntityTrigger] string context) { } } @@ -99,7 +104,7 @@ public static async Task RunOrchestrator( Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 11, 29) + new DiagnosticResultLocation("Test0.cs", 13, 29) } }; @@ -108,21 +113,23 @@ public static async Task RunOrchestrator( VerifyCSharpFix(test, ExpectedFix); } + // Tests SyntaxKind.GenericName [TestMethod] - public void EntityTrigger_Tuple() + public void EntityTrigger_UsingList() { var test = @" +using System.Threading.Tasks; +using System.Collections.Generic; +using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.DurableTask; -using Microsoft.Extensions.Logging; -namespace ExternalInteraction +namespace VSSample { - public static class HireEmployee + public static class HelloSequence { - [FunctionName(""HireEmployee"")] - public static async Task RunOrchestrator( - [EntityTrigger] Tuple context, - ILogger log) + [FunctionName(""EntityContextAnalyzerTestCases"")] + public static async Task Run( + [EntityTrigger] List context) { } } @@ -130,11 +137,122 @@ public static async Task RunOrchestrator( var expectedDiagnostics = new DiagnosticResult { Id = DiagnosticId, - Message = string.Format(Resources.EntityContextAnalyzerMessageFormat, "Tuple"), + Message = string.Format(Resources.EntityContextAnalyzerMessageFormat, "List"), Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 11, 29) + new DiagnosticResultLocation("Test0.cs", 13, 29) + } + }; + + VerifyCSharpDiagnostic(test, expectedDiagnostics); + + VerifyCSharpFix(test, ExpectedFix, allowNewCompilerDiagnostics: true); + } + + // Tests SyntaxKind.ArrayType + [TestMethod] + public void EntityTrigger_UsingArray() + { + var test = @" +using System.Threading.Tasks; +using System.Collections.Generic; +using Microsoft.Azure.WebJobs; +using Microsoft.Azure.WebJobs.Extensions.DurableTask; + +namespace VSSample +{ + public static class HelloSequence + { + [FunctionName(""EntityContextAnalyzerTestCases"")] + public static async Task Run( + [EntityTrigger] string[] context) + { + } + } +}"; + var expectedDiagnostics = new DiagnosticResult + { + Id = DiagnosticId, + Message = string.Format(Resources.EntityContextAnalyzerMessageFormat, "string[]"), + Severity = Severity, + Locations = + new[] { + new DiagnosticResultLocation("Test0.cs", 13, 29) + } + }; + + VerifyCSharpDiagnostic(test, expectedDiagnostics); + + VerifyCSharpFix(test, ExpectedFix); + } + + // Tests SyntaxKind.TupleType + [TestMethod] + public void EntityTrigger_UsingValueTuple() + { + var test = @" +using System.Threading.Tasks; +using System.Collections.Generic; +using Microsoft.Azure.WebJobs; +using Microsoft.Azure.WebJobs.Extensions.DurableTask; + +namespace VSSample +{ + public static class HelloSequence + { + [FunctionName(""EntityContextAnalyzerTestCases"")] + public static async Task Run( + [EntityTrigger] (string, int) context) + { + } + } +}"; + var expectedDiagnostics = new DiagnosticResult + { + Id = DiagnosticId, + Message = string.Format(Resources.EntityContextAnalyzerMessageFormat, "(string, int)"), + Severity = Severity, + Locations = + new[] { + new DiagnosticResultLocation("Test0.cs", 13, 29) + } + }; + + VerifyCSharpDiagnostic(test, expectedDiagnostics); + + VerifyCSharpFix(test, ExpectedFix); + } + + // Tests Incorrect Durable Type + [TestMethod] + public void EntityTrigger_UsingIncorrectDurableType() + { + var test = @" +using System.Threading.Tasks; +using System.Collections.Generic; +using Microsoft.Azure.WebJobs; +using Microsoft.Azure.WebJobs.Extensions.DurableTask; + +namespace VSSample +{ + public static class HelloSequence + { + [FunctionName(""EntityContextAnalyzerTestCases"")] + public static async Task Run( + [EntityTrigger] IDurableActivityContext context) + { + } + } +}"; + var expectedDiagnostics = new DiagnosticResult + { + Id = DiagnosticId, + Message = string.Format(Resources.EntityContextAnalyzerMessageFormat, "IDurableActivityContext"), + Severity = Severity, + Locations = + new[] { + new DiagnosticResultLocation("Test0.cs", 13, 29) } }; diff --git a/test/WebJobs.Extensions.DurableTask.Analyzers.Test/Binding/OrchestratorContextAnalyzerTests.cs b/test/WebJobs.Extensions.DurableTask.Analyzers.Test/Binding/OrchestratorContextAnalyzerTests.cs index 296eb94a2..7f27447fa 100644 --- a/test/WebJobs.Extensions.DurableTask.Analyzers.Test/Binding/OrchestratorContextAnalyzerTests.cs +++ b/test/WebJobs.Extensions.DurableTask.Analyzers.Test/Binding/OrchestratorContextAnalyzerTests.cs @@ -13,64 +13,62 @@ namespace Microsoft.Azure.WebJobs.Extensions.DurableTask.Analyzers.Test.Binding public class OrchestratorContextAnalyzerTests : CodeFixVerifier { private static readonly string DiagnosticId = OrchestratorContextAnalyzer.DiagnosticId; - private static readonly DiagnosticSeverity Severity = OrchestratorContextAnalyzer.Severity; + private static readonly DiagnosticSeverity Severity = DiagnosticSeverity.Warning; private const string V1ExpectedFix = @" +using System.Threading.Tasks; +using System.Collections.Generic; using Microsoft.Azure.WebJobs; -using Microsoft.Extensions.Logging; -namespace ExternalInteraction +namespace VSSample { - public static class HireEmployee + public static class HelloSequence { - [FunctionName(""HireEmployee"")] - public static async Task RunOrchestrator( - [OrchestrationTrigger] DurableOrchestrationContext context, - ILogger log) + [FunctionName(""OrchestratorContextAnalyzerTestCases"")] + public static async Task Run( + [OrchestrationTrigger] DurableOrchestrationContext context) { - } } }"; private const string V1BaseExpectedFix = @" +using System.Threading.Tasks; +using System.Collections.Generic; using Microsoft.Azure.WebJobs; -using Microsoft.Extensions.Logging; -namespace ExternalInteraction +namespace VSSample { - public static class HireEmployee + public static class HelloSequence { - [FunctionName(""HireEmployee"")] - public static async Task RunOrchestrator( - [OrchestrationTrigger] DurableOrchestrationContextBase context, - ILogger log) + [FunctionName(""OrchestratorContextAnalyzerTestCases"")] + public static async Task Run( + [OrchestrationTrigger] DurableOrchestrationContextBase context) { - } } }"; private const string V2ExpectedFix = @" +using System.Threading.Tasks; +using System.Collections.Generic; +using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.DurableTask; -using Microsoft.Extensions.Logging; -namespace ExternalInteraction +namespace VSSample { - public static class HireEmployee + public static class HelloSequence { - [FunctionName(""HireEmployee"")] - public static async Task RunOrchestrator( - [OrchestrationTrigger] IDurableOrchestrationContext context, - ILogger log) + [FunctionName(""OrchestratorContextAnalyzerTestCases"")] + public static async Task Run( + [OrchestrationTrigger] IDurableOrchestrationContext context) { - } } }"; [TestMethod] - public void OrchestrationContext_V1_NonIssue() + public void OrchestrationContext_V1_NoDiagnosticTestCases() { SyntaxNodeUtils.version = DurableVersion.V1; VerifyCSharpDiagnostic(V1ExpectedFix); @@ -78,30 +76,29 @@ public void OrchestrationContext_V1_NonIssue() } [TestMethod] - public void OrchestrationContext_V2_NonIssue() + public void OrchestrationContext_V2_NoDiagnosticTestCases() { SyntaxNodeUtils.version = DurableVersion.V2; VerifyCSharpDiagnostic(V2ExpectedFix); } - + // Tests SyntaxKind.IdentifierName [TestMethod] - public void OrchestrationContext_V1_Object() + public void OrchestrationContext_V1_UsingObject() { var test = @" +using System.Threading.Tasks; +using System.Collections.Generic; using Microsoft.Azure.WebJobs; -using Microsoft.Extensions.Logging; -namespace ExternalInteraction +namespace VSSample { - public static class HireEmployee + public static class HelloSequence { - [FunctionName(""HireEmployee"")] - public static async Task RunOrchestrator( - [OrchestrationTrigger] Object context, - ILogger log) + [FunctionName(""OrchestratorContextAnalyzerTestCases"")] + public static async Task Run( + [OrchestrationTrigger] Object context) { - } } }"; @@ -112,7 +109,7 @@ public static async Task RunOrchestrator( Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 11, 36) + new DiagnosticResultLocation("Test0.cs", 12, 36) } }; @@ -124,23 +121,23 @@ public static async Task RunOrchestrator( VerifyCSharpFix(test, V1BaseExpectedFix, 1); } + // Tests SyntaxKind.PredefinedType [TestMethod] - public void OrchestrationContext_V1_String() + public void OrchestrationContext_V1_UsingString() { var test = @" +using System.Threading.Tasks; +using System.Collections.Generic; using Microsoft.Azure.WebJobs; -using Microsoft.Extensions.Logging; -namespace ExternalInteraction +namespace VSSample { - public static class HireEmployee + public static class HelloSequence { - [FunctionName(""HireEmployee"")] - public static async Task RunOrchestrator( - [OrchestrationTrigger] string context, - ILogger log) + [FunctionName(""OrchestratorContextAnalyzerTestCases"")] + public static async Task Run( + [OrchestrationTrigger] string context) { - } } }"; @@ -151,7 +148,7 @@ public static async Task RunOrchestrator( Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 11, 36) + new DiagnosticResultLocation("Test0.cs", 12, 36) } }; @@ -163,34 +160,34 @@ public static async Task RunOrchestrator( VerifyCSharpFix(test, V1BaseExpectedFix, 1); } + // Tests SyntaxKind.GenericName [TestMethod] - public void OrchestrationContext_V1_Tuple() + public void OrchestrationContext_V1_UsingList() { var test = @" +using System.Threading.Tasks; +using System.Collections.Generic; using Microsoft.Azure.WebJobs; -using Microsoft.Extensions.Logging; -namespace ExternalInteraction +namespace VSSample { - public static class HireEmployee + public static class HelloSequence { - [FunctionName(""HireEmployee"")] - public static async Task RunOrchestrator( - [OrchestrationTrigger] Tuple context, - ILogger log) + [FunctionName(""OrchestratorContextAnalyzerTestCases"")] + public static async Task Run( + [OrchestrationTrigger] List context) { - } } }"; var expectedDiagnostics = new DiagnosticResult { Id = DiagnosticId, - Message = string.Format(Resources.V1OrchestratorContextAnalyzerMessageFormat, "Tuple"), + Message = string.Format(Resources.V1OrchestratorContextAnalyzerMessageFormat, "List"), Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 11, 36) + new DiagnosticResultLocation("Test0.cs", 12, 36) } }; @@ -202,23 +199,101 @@ public static async Task RunOrchestrator( VerifyCSharpFix(test, V1BaseExpectedFix, 1); } + // Tests SyntaxKind.ArrayType [TestMethod] - public void OrchestrationContext_V1_V2DurableInterface() + public void OrchestrationContext_V1_UsingArray() { var test = @" +using System.Threading.Tasks; +using System.Collections.Generic; using Microsoft.Azure.WebJobs; -using Microsoft.Extensions.Logging; -namespace ExternalInteraction +namespace VSSample { - public static class HireEmployee + public static class HelloSequence { - [FunctionName(""HireEmployee"")] - public static async Task RunOrchestrator( - [OrchestrationTrigger] IDurableOrchestrationContext context, - ILogger log) + [FunctionName(""OrchestratorContextAnalyzerTestCases"")] + public static async Task Run( + [OrchestrationTrigger] string[] context) + { + } + } +}"; + var expectedDiagnostics = new DiagnosticResult + { + Id = DiagnosticId, + Message = string.Format(Resources.V1OrchestratorContextAnalyzerMessageFormat, "string[]"), + Severity = Severity, + Locations = + new[] { + new DiagnosticResultLocation("Test0.cs", 12, 36) + } + }; + + SyntaxNodeUtils.version = DurableVersion.V1; + + VerifyCSharpDiagnostic(test, expectedDiagnostics); + + VerifyCSharpFix(test, V1ExpectedFix, 0); + VerifyCSharpFix(test, V1BaseExpectedFix, 1); + } + + // Tests SyntaxKind.TupleType + [TestMethod] + public void OrchestrationContext_V1_UsingValueTuple() + { + var test = @" +using System.Threading.Tasks; +using System.Collections.Generic; +using Microsoft.Azure.WebJobs; + +namespace VSSample +{ + public static class HelloSequence + { + [FunctionName(""OrchestratorContextAnalyzerTestCases"")] + public static async Task Run( + [OrchestrationTrigger] (string, int) context) + { + } + } +}"; + var expectedDiagnostics = new DiagnosticResult + { + Id = DiagnosticId, + Message = string.Format(Resources.V1OrchestratorContextAnalyzerMessageFormat, "(string, int)"), + Severity = Severity, + Locations = + new[] { + new DiagnosticResultLocation("Test0.cs", 12, 36) + } + }; + + SyntaxNodeUtils.version = DurableVersion.V1; + + VerifyCSharpDiagnostic(test, expectedDiagnostics); + + VerifyCSharpFix(test, V1ExpectedFix, 0); + VerifyCSharpFix(test, V1BaseExpectedFix, 1); + } + + // Tests Durable V2 Context + [TestMethod] + public void OrchestrationContext_V1_UsingV2Context() + { + var test = @" +using System.Threading.Tasks; +using System.Collections.Generic; +using Microsoft.Azure.WebJobs; + +namespace VSSample +{ + public static class HelloSequence + { + [FunctionName(""OrchestratorContextAnalyzerTestCases"")] + public static async Task Run( + [OrchestrationTrigger] IDurableOrchestrationContext context) { - } } }"; @@ -229,7 +304,7 @@ public static async Task RunOrchestrator( Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 11, 36) + new DiagnosticResultLocation("Test0.cs", 12, 36) } }; @@ -241,23 +316,24 @@ public static async Task RunOrchestrator( VerifyCSharpFix(test, V1BaseExpectedFix, 1); } + // Tests SyntaxKind.IdentifierName [TestMethod] - public void OrchestrationContext_V2_Object() + public void OrchestrationContext_V2_UsingObject() { var test = @" +using System.Threading.Tasks; +using System.Collections.Generic; +using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.DurableTask; -using Microsoft.Extensions.Logging; -namespace ExternalInteraction +namespace VSSample { - public static class HireEmployee + public static class HelloSequence { - [FunctionName(""HireEmployee"")] - public static async Task RunOrchestrator( - [OrchestrationTrigger] Object context, - ILogger log) + [FunctionName(""OrchestratorContextAnalyzerTestCases"")] + public static async Task Run( + [OrchestrationTrigger] Object context) { - } } }"; @@ -268,7 +344,7 @@ public static async Task RunOrchestrator( Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 11, 36) + new DiagnosticResultLocation("Test0.cs", 13, 36) } }; @@ -279,23 +355,24 @@ public static async Task RunOrchestrator( VerifyCSharpFix(test, V2ExpectedFix); } + // Tests SyntaxKind.PredefinedType [TestMethod] - public void OrchestrationContext_V2_String() + public void OrchestrationContext_V2_UsingString() { var test = @" +using System.Threading.Tasks; +using System.Collections.Generic; +using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.DurableTask; -using Microsoft.Extensions.Logging; -namespace ExternalInteraction +namespace VSSample { - public static class HireEmployee + public static class HelloSequence { - [FunctionName(""HireEmployee"")] - public static async Task RunOrchestrator( - [OrchestrationTrigger] string context, - ILogger log) + [FunctionName(""OrchestratorContextAnalyzerTestCases"")] + public static async Task Run( + [OrchestrationTrigger] string context) { - } } }"; @@ -306,7 +383,7 @@ public static async Task RunOrchestrator( Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 11, 36) + new DiagnosticResultLocation("Test0.cs", 13, 36) } }; @@ -317,34 +394,113 @@ public static async Task RunOrchestrator( VerifyCSharpFix(test, V2ExpectedFix); } + // Tests SyntaxKind.GenericName [TestMethod] - public void OrchestrationContext_V2_Tuple() + public void OrchestrationContext_V2_UsingList() { var test = @" +using System.Threading.Tasks; +using System.Collections.Generic; +using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.DurableTask; -using Microsoft.Extensions.Logging; -namespace ExternalInteraction +namespace VSSample { - public static class HireEmployee + public static class HelloSequence { - [FunctionName(""HireEmployee"")] - public static async Task RunOrchestrator( - [OrchestrationTrigger] Tuple context, - ILogger log) + [FunctionName(""OrchestratorContextAnalyzerTestCases"")] + public static async Task Run( + [OrchestrationTrigger] List context) { - } } }"; var expectedDiagnostics = new DiagnosticResult { Id = DiagnosticId, - Message = string.Format(Resources.V2OrchestratorContextAnalyzerMessageFormat, "Tuple"), + Message = string.Format(Resources.V2OrchestratorContextAnalyzerMessageFormat, "List"), Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 11, 36) + new DiagnosticResultLocation("Test0.cs", 13, 36) + } + }; + + SyntaxNodeUtils.version = DurableVersion.V2; + + VerifyCSharpDiagnostic(test, expectedDiagnostics); + + VerifyCSharpFix(test, V2ExpectedFix, allowNewCompilerDiagnostics: true); + } + + // Tests SyntaxKind.ArrayType + [TestMethod] + public void OrchestrationContext_V2_UsingArray() + { + var test = @" +using System.Threading.Tasks; +using System.Collections.Generic; +using Microsoft.Azure.WebJobs; +using Microsoft.Azure.WebJobs.Extensions.DurableTask; + +namespace VSSample +{ + public static class HelloSequence + { + [FunctionName(""OrchestratorContextAnalyzerTestCases"")] + public static async Task Run( + [OrchestrationTrigger] string[] context) + { + } + } +}"; + var expectedDiagnostics = new DiagnosticResult + { + Id = DiagnosticId, + Message = string.Format(Resources.V2OrchestratorContextAnalyzerMessageFormat, "string[]"), + Severity = Severity, + Locations = + new[] { + new DiagnosticResultLocation("Test0.cs", 13, 36) + } + }; + + SyntaxNodeUtils.version = DurableVersion.V2; + + VerifyCSharpDiagnostic(test, expectedDiagnostics); + + VerifyCSharpFix(test, V2ExpectedFix); + } + + // Tests SyntaxKind.TupleType + [TestMethod] + public void OrchestrationContext_V2_UsingValueTuple() + { + var test = @" +using System.Threading.Tasks; +using System.Collections.Generic; +using Microsoft.Azure.WebJobs; +using Microsoft.Azure.WebJobs.Extensions.DurableTask; + +namespace VSSample +{ + public static class HelloSequence + { + [FunctionName(""OrchestratorContextAnalyzerTestCases"")] + public static async Task Run( + [OrchestrationTrigger] (string, int) context) + { + } + } +}"; + var expectedDiagnostics = new DiagnosticResult + { + Id = DiagnosticId, + Message = string.Format(Resources.V2OrchestratorContextAnalyzerMessageFormat, "(string, int)"), + Severity = Severity, + Locations = + new[] { + new DiagnosticResultLocation("Test0.cs", 13, 36) } }; @@ -355,23 +511,24 @@ public static async Task RunOrchestrator( VerifyCSharpFix(test, V2ExpectedFix); } + // Tests Durable V1 Context [TestMethod] - public void OrchestrationContext_V2_V1DurableClass() + public void OrchestrationContext_V2_UsingV1Context() { var test = @" +using System.Threading.Tasks; +using System.Collections.Generic; +using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.DurableTask; -using Microsoft.Extensions.Logging; -namespace ExternalInteraction +namespace VSSample { - public static class HireEmployee + public static class HelloSequence { - [FunctionName(""HireEmployee"")] - public static async Task RunOrchestrator( - [OrchestrationTrigger] DurableOrchestrationContext context, - ILogger log) + [FunctionName(""OrchestratorContextAnalyzerTestCases"")] + public static async Task Run( + [OrchestrationTrigger] DurableOrchestrationContext context) { - } } }"; @@ -382,7 +539,7 @@ public static async Task RunOrchestrator( Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 11, 36) + new DiagnosticResultLocation("Test0.cs", 13, 36) } }; @@ -393,6 +550,26 @@ public static async Task RunOrchestrator( VerifyCSharpFix(test, V2ExpectedFix); } + // Tests SyntaxNodeUtils.TryGetAttribute + [TestMethod] + public void OrchestrationContext_V2_InvalidOperationTest() + { + var test = @" +namespace VSSample +{ + public interface ExampleInterface + { + [return: System.ServiceModel.MessageParameterAttribute(Name = ""getSubscriberInfoReturn"")] + string getSubscriberInfo(string request); + } +} +"; + + SyntaxNodeUtils.version = DurableVersion.V2; + + VerifyCSharpDiagnostic(test, new DiagnosticResult[] { }); + } + protected override CodeFixProvider GetCSharpCodeFixProvider() { return new OrchestratorContextCodeFixProvider(); diff --git a/test/WebJobs.Extensions.DurableTask.Analyzers.Test/Entity/ClassNameAnalyzerTests.cs b/test/WebJobs.Extensions.DurableTask.Analyzers.Test/Entity/ClassNameAnalyzerTests.cs index 3bc5f7690..2f5e95a00 100644 --- a/test/WebJobs.Extensions.DurableTask.Analyzers.Test/Entity/ClassNameAnalyzerTests.cs +++ b/test/WebJobs.Extensions.DurableTask.Analyzers.Test/Entity/ClassNameAnalyzerTests.cs @@ -12,29 +12,24 @@ namespace Microsoft.Azure.WebJobs.Extensions.DurableTask.Analyzers.Test.Entity public class ClassNameAnalyzerTests : CodeFixVerifier { private static readonly string DiagnosticId = ClassNameAnalyzer.DiagnosticId; - private static readonly DiagnosticSeverity Severity = ClassNameAnalyzer.Severity; + private static readonly DiagnosticSeverity Severity = DiagnosticSeverity.Warning; [TestMethod] - public void ClassName_NonIssue() + public void ClassName_NoDiagnosticMatch() { var test = @" -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; using System.Threading.Tasks; +using System.Collections.Generic; using Microsoft.Azure.WebJobs; -using Microsoft.Azure.WebJobs.Extensions.Http; -using Microsoft.Azure.WebJobs.Host; -using Microsoft.Extensions.Logging; +using Microsoft.Azure.WebJobs.Extensions.DurableTask; -namespace ExternalInteraction +namespace VSSample { - public static class HireEmployee + public static class HelloSequence { - [FunctionName(""HireEmployee"")] - public static async Task RunOrchestrator( - [EntityTrigger] IDurableEntityContext context, - ILogger log) + [FunctionName(""HelloSequence"")] + public static async Task Run( + [EntityTrigger] IDurableEntityContext context) { } }"; @@ -42,26 +37,21 @@ public static async Task RunOrchestrator( } [TestMethod] - public void ClassName_NonIssue_NameOf() + public void ClassName_NoDiagnosticNameOf() { var test = @" -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; using System.Threading.Tasks; +using System.Collections.Generic; using Microsoft.Azure.WebJobs; -using Microsoft.Azure.WebJobs.Extensions.Http; -using Microsoft.Azure.WebJobs.Host; -using Microsoft.Extensions.Logging; +using Microsoft.Azure.WebJobs.Extensions.DurableTask; -namespace ExternalInteraction +namespace VSSample { - public static class HireEmployee + public static class HelloSequence { - [FunctionName(nameof(HireEmployee))] - public static async Task RunOrchestrator( - [EntityTrigger] IDurableEntityContext context, - ILogger log) + [FunctionName(nameof(HelloSequence))] + public static async Task Run( + [EntityTrigger] IDurableEntityContext context) { } }"; @@ -70,26 +60,21 @@ public static async Task RunOrchestrator( } [TestMethod] - public void ClassName_NonIssue_NameOf_Namespace() + public void ClassName_NoDiagnosticNameOfWithNamespace() { var test = @" -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; using System.Threading.Tasks; +using System.Collections.Generic; using Microsoft.Azure.WebJobs; -using Microsoft.Azure.WebJobs.Extensions.Http; -using Microsoft.Azure.WebJobs.Host; -using Microsoft.Extensions.Logging; +using Microsoft.Azure.WebJobs.Extensions.DurableTask; -namespace ExternalInteraction +namespace VSSample { - public static class HireEmployee + public static class HelloSequence { - [FunctionName(nameof(ExternalInteraction.HireEmployee))] - public static async Task RunOrchestrator( - [EntityTrigger] IDurableEntityContext context, - ILogger log) + [FunctionName(nameof(VSSample.HelloSequence))] + public static async Task Run( + [EntityTrigger] IDurableEntityContext context) { } }"; @@ -101,23 +86,18 @@ public static async Task RunOrchestrator( public void ClasName_Mismatch() { var test = @" -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; using System.Threading.Tasks; +using System.Collections.Generic; using Microsoft.Azure.WebJobs; -using Microsoft.Azure.WebJobs.Extensions.Http; -using Microsoft.Azure.WebJobs.Host; -using Microsoft.Extensions.Logging; +using Microsoft.Azure.WebJobs.Extensions.DurableTask; -namespace ExternalInteraction +namespace VSSample { - public static class HireEmployee + public static class HelloSequence { [FunctionName(""HelloWorld"")] - public static async Task RunOrchestrator( - [EntityTrigger] IDurableEntityContext context, - ILogger log) + public static async Task Run( + [EntityTrigger] IDurableEntityContext context) { } }"; @@ -125,11 +105,11 @@ public static async Task RunOrchestrator( var expectedResults = new DiagnosticResult { Id = DiagnosticId, - Message = string.Format(Resources.EntityClassNameAnalyzerCloseMessageFormat, "HelloWorld", "HireEmployee"), + Message = string.Format(Resources.EntityClassNameAnalyzerCloseMessageFormat, "HelloWorld", "HelloSequence"), Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 15, 23) + new DiagnosticResultLocation("Test0.cs", 11, 23) } }; diff --git a/test/WebJobs.Extensions.DurableTask.Analyzers.Test/Entity/DispatchEntityNameAnalyzerTests.cs b/test/WebJobs.Extensions.DurableTask.Analyzers.Test/Entity/DispatchEntityNameAnalyzerTests.cs index de6af8193..49b6208dd 100644 --- a/test/WebJobs.Extensions.DurableTask.Analyzers.Test/Entity/DispatchEntityNameAnalyzerTests.cs +++ b/test/WebJobs.Extensions.DurableTask.Analyzers.Test/Entity/DispatchEntityNameAnalyzerTests.cs @@ -13,9 +13,11 @@ namespace Microsoft.Azure.WebJobs.Extensions.DurableTask.Analyzers.Test.Entity public class DispatchEntityNameAnalyzerTests : CodeFixVerifier { private static readonly string DiagnosticId = DispatchEntityNameAnalyzer.DiagnosticId; - private static readonly DiagnosticSeverity Severity = DispatchEntityNameAnalyzer.Severity; + private static readonly DiagnosticSeverity Severity = DiagnosticSeverity.Warning; private const string ExpectedFix = @" +using System.Threading.Tasks; +using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.DurableTask; public class MyEmptyEntity : IMyEmptyEntity @@ -25,15 +27,18 @@ public class MyEmptyEntity : IMyEmptyEntity }"; [TestMethod] - public void DispatchCall_NonIssue() + public void DispatchCall_NoDiagnosticTestCases() { VerifyCSharpDiagnostic(ExpectedFix); } + // Tests SyntaxKind.IdentifierName [TestMethod] - public void DispatchCall_Object() + public void DispatchCall_UsingObject() { var test = @" +using System.Threading.Tasks; +using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.DurableTask; public class MyEmptyEntity : IMyEmptyEntity @@ -48,7 +53,7 @@ public class MyEmptyEntity : IMyEmptyEntity Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 7, 96) + new DiagnosticResultLocation("Test0.cs", 9, 96) } }; @@ -57,10 +62,13 @@ public class MyEmptyEntity : IMyEmptyEntity VerifyCSharpFix(test, ExpectedFix); } + // Tests SyntaxKind.PredefinedType [TestMethod] - public void DispatchCall_String() + public void DispatchCall_UsingString() { var test = @" +using System.Threading.Tasks; +using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.DurableTask; public class MyEmptyEntity : IMyEmptyEntity @@ -75,7 +83,7 @@ public class MyEmptyEntity : IMyEmptyEntity Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 7, 96) + new DiagnosticResultLocation("Test0.cs", 9, 96) } }; @@ -84,25 +92,58 @@ public class MyEmptyEntity : IMyEmptyEntity VerifyCSharpFix(test, ExpectedFix); } + // Tests SyntaxKind.GenericName [TestMethod] - public void DispatchCall_ILogger() + public void DispatchCall_UsingList() { var test = @" +using System.Threading.Tasks; +using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.DurableTask; public class MyEmptyEntity : IMyEmptyEntity { [FunctionName(""MyEmptyEntity"")] - public static Task Run([EntityTrigger] IDurableEntityContext ctx) => ctx.DispatchAsync(); + public static Task Run([EntityTrigger] IDurableEntityContext ctx) => ctx.DispatchAsync>(); }"; var expectedDiagnostics = new DiagnosticResult { Id = DiagnosticId, - Message = string.Format(Resources.DispatchEntityNameAnalyzerMessageFormat, "ILogger", "MyEmptyEntity"), + Message = string.Format(Resources.DispatchEntityNameAnalyzerMessageFormat, "List", "MyEmptyEntity"), + Severity = Severity, + Locations = + new[] { + new DiagnosticResultLocation("Test0.cs", 9, 96) + } + }; + + VerifyCSharpDiagnostic(test, expectedDiagnostics); + + VerifyCSharpFix(test, ExpectedFix); + } + + // Tests SyntaxKind.ArrayType + [TestMethod] + public void DispatchCall_UsingArray() + { + var test = @" +using System.Threading.Tasks; +using Microsoft.Azure.WebJobs; +using Microsoft.Azure.WebJobs.Extensions.DurableTask; + + public class MyEmptyEntity : IMyEmptyEntity + { + [FunctionName(""MyEmptyEntity"")] + public static Task Run([EntityTrigger] IDurableEntityContext ctx) => ctx.DispatchAsync(); + }"; + var expectedDiagnostics = new DiagnosticResult + { + Id = DiagnosticId, + Message = string.Format(Resources.DispatchEntityNameAnalyzerMessageFormat, "string[]", "MyEmptyEntity"), Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 7, 96) + new DiagnosticResultLocation("Test0.cs", 9, 96) } }; @@ -111,25 +152,58 @@ public class MyEmptyEntity : IMyEmptyEntity VerifyCSharpFix(test, ExpectedFix); } + // Tests SyntaxKind.TupleType [TestMethod] - public void DispatchCall_Tuple() + public void DispatchCall_UsingValueTuple() { var test = @" +using System.Threading.Tasks; +using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.DurableTask; public class MyEmptyEntity : IMyEmptyEntity { [FunctionName(""MyEmptyEntity"")] - public static Task Run([EntityTrigger] IDurableEntityContext ctx) => ctx.DispatchAsync>(); + public static Task Run([EntityTrigger] IDurableEntityContext ctx) => ctx.DispatchAsync<(string, int)>(); }"; var expectedDiagnostics = new DiagnosticResult { Id = DiagnosticId, - Message = string.Format(Resources.DispatchEntityNameAnalyzerMessageFormat, "Tuple", "MyEmptyEntity"), + Message = string.Format(Resources.DispatchEntityNameAnalyzerMessageFormat, "(string, int)", "MyEmptyEntity"), + Severity = Severity, + Locations = + new[] { + new DiagnosticResultLocation("Test0.cs", 9, 96) + } + }; + + VerifyCSharpDiagnostic(test, expectedDiagnostics); + + VerifyCSharpFix(test, ExpectedFix); + } + + // Tests interface not defined in user code + [TestMethod] + public void DispatchCall_ILogger() + { + var test = @" +using System.Threading.Tasks; +using Microsoft.Azure.WebJobs; +using Microsoft.Azure.WebJobs.Extensions.DurableTask; + + public class MyEmptyEntity : IMyEmptyEntity + { + [FunctionName(""MyEmptyEntity"")] + public static Task Run([EntityTrigger] IDurableEntityContext ctx) => ctx.DispatchAsync(); + }"; + var expectedDiagnostics = new DiagnosticResult + { + Id = DiagnosticId, + Message = string.Format(Resources.DispatchEntityNameAnalyzerMessageFormat, "ILogger", "MyEmptyEntity"), Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 7, 96) + new DiagnosticResultLocation("Test0.cs", 9, 96) } }; diff --git a/test/WebJobs.Extensions.DurableTask.Analyzers.Test/Entity/StaticFunctionAnalyzerTests.cs b/test/WebJobs.Extensions.DurableTask.Analyzers.Test/Entity/StaticFunctionAnalyzerTests.cs index e14de114f..44cd939a2 100644 --- a/test/WebJobs.Extensions.DurableTask.Analyzers.Test/Entity/StaticFunctionAnalyzerTests.cs +++ b/test/WebJobs.Extensions.DurableTask.Analyzers.Test/Entity/StaticFunctionAnalyzerTests.cs @@ -13,9 +13,10 @@ namespace Microsoft.Azure.WebJobs.Extensions.DurableTask.Analyzers.Test.Entity public class StaticFunctionAnalyzerTests : CodeFixVerifier { private static readonly string DiagnosticId = StaticFunctionAnalyzer.DiagnosticId; - private static readonly DiagnosticSeverity Severity = StaticFunctionAnalyzer.Severity; + private static readonly DiagnosticSeverity Severity = DiagnosticSeverity.Warning; private const string ExpectedFix = @" +using System.Threading.Tasks; using Microsoft.Azure.WebJobs.Extensions.DurableTask; public class MyEmptyEntity : IMyEmptyEntity @@ -25,15 +26,16 @@ public class MyEmptyEntity : IMyEmptyEntity }"; [TestMethod] - public void StaticFunction_NonIssue() + public void StaticFunction_NoDiagnosticTestCases() { VerifyCSharpDiagnostic(ExpectedFix); } [TestMethod] - public void StaticFunction_NonStatic() + public void StaticFunction_NotStatic() { var test = @" +using System.Threading.Tasks; using Microsoft.Azure.WebJobs.Extensions.DurableTask; public class MyEmptyEntity : IMyEmptyEntity @@ -49,7 +51,7 @@ public class MyEmptyEntity : IMyEmptyEntity Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 7, 21) + new DiagnosticResultLocation("Test0.cs", 8, 21) } }; diff --git a/test/WebJobs.Extensions.DurableTask.Analyzers.Test/EntityInterface/EntityInterfaceReturnTypeAnalyzerTests.cs b/test/WebJobs.Extensions.DurableTask.Analyzers.Test/EntityInterface/EntityInterfaceReturnTypeAnalyzerTests.cs index 5719eddf8..89c3f1c91 100644 --- a/test/WebJobs.Extensions.DurableTask.Analyzers.Test/EntityInterface/EntityInterfaceReturnTypeAnalyzerTests.cs +++ b/test/WebJobs.Extensions.DurableTask.Analyzers.Test/EntityInterface/EntityInterfaceReturnTypeAnalyzerTests.cs @@ -12,23 +12,23 @@ namespace Microsoft.Azure.WebJobs.Extensions.DurableTask.Analyzers.Test.EntityIn public class EntityInterfaceReturnTypeAnalyzerTests : CodeFixVerifier { private static readonly string DiagnosticId = EntityInterfaceReturnTypeAnalyzer.DiagnosticId; - private static readonly DiagnosticSeverity Severity = EntityInterfaceReturnTypeAnalyzer.Severity; + private static readonly DiagnosticSeverity Severity = DiagnosticSeverity.Warning; [TestMethod] - public void ReturnTypeAnalyzer_NonIssue() + public void ReturnTypeAnalyzer_NoDiagnosticTestCases() { var test = @" - using System; - using System.Collections.Generic; using System.Threading.Tasks; + using System.Collections.Generic; using Microsoft.Azure.WebJobs; + using Microsoft.Azure.WebJobs.Extensions.DurableTask; namespace VSSample { public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + [FunctionName(""ReturnTypeAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { context.SignalEntityAsync(); @@ -48,21 +48,22 @@ public interface IEntityExample VerifyCSharpDiagnostic(test); } + // Tests SyntaxKind.IdentifierName [TestMethod] public void ReturnTypeAnalyzer_IncorrectReturn_Object() { var test = @" - using System; - using System.Collections.Generic; using System.Threading.Tasks; + using System.Collections.Generic; using Microsoft.Azure.WebJobs; + using Microsoft.Azure.WebJobs.Extensions.DurableTask; namespace VSSample { public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + [FunctionName(""ReturnTypeAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { context.SignalEntityAsync(); @@ -88,21 +89,22 @@ public interface IEntityExample VerifyCSharpDiagnostic(test, expectedDiagnostics); } + // Tests SyntaxKind.PredefinedType [TestMethod] public void ReturnTypeAnalyzer_IncorrectReturn_string() { var test = @" - using System; - using System.Collections.Generic; using System.Threading.Tasks; + using System.Collections.Generic; using Microsoft.Azure.WebJobs; + using Microsoft.Azure.WebJobs.Extensions.DurableTask; namespace VSSample { public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + [FunctionName(""ReturnTypeAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { context.SignalEntityAsync(); @@ -128,21 +130,104 @@ public interface IEntityExample VerifyCSharpDiagnostic(test, expectedDiagnostics); } + // Tests SyntaxKind.GenericName + [TestMethod] + public void ReturnTypeAnalyzer_IncorrectReturn_List() + { + var test = @" + using System.Threading.Tasks; + using System.Collections.Generic; + using Microsoft.Azure.WebJobs; + using Microsoft.Azure.WebJobs.Extensions.DurableTask; + + namespace VSSample + { + public static class HelloSequence + { + [FunctionName(""ReturnTypeAnalyzerTestCases"")] + public static async Task Run( + [OrchestrationTrigger] IDurableOrchestrationContext context) + { + context.SignalEntityAsync(); + } + } + + public interface IEntityExample + { + public static List methodTestOneParameter(string test); + } + }"; + var expectedDiagnostics = new DiagnosticResult + { + Id = DiagnosticId, + Message = string.Format(Resources.EntityInterfaceReturnTypeAnalyzerMessageFormat, "List"), + Severity = Severity, + Locations = + new[] { + new DiagnosticResultLocation("Test0.cs", 21, 13) + } + }; + + VerifyCSharpDiagnostic(test, expectedDiagnostics); + } + + // Tests SyntaxKind.ArrayType [TestMethod] - public void ReturnTypeAnalyzer_IncorrectReturn_Tuple() + public void ReturnTypeAnalyzer_IncorrectReturn_Array() { var test = @" - using System; + using System.Threading.Tasks; using System.Collections.Generic; + using Microsoft.Azure.WebJobs; + using Microsoft.Azure.WebJobs.Extensions.DurableTask; + + namespace VSSample + { + public static class HelloSequence + { + [FunctionName(""ReturnTypeAnalyzerTestCases"")] + public static async Task Run( + [OrchestrationTrigger] IDurableOrchestrationContext context) + { + context.SignalEntityAsync(); + } + } + + public interface IEntityExample + { + public static string[] methodTestOneParameter(string test); + } + }"; + var expectedDiagnostics = new DiagnosticResult + { + Id = DiagnosticId, + Message = string.Format(Resources.EntityInterfaceReturnTypeAnalyzerMessageFormat, "string[]"), + Severity = Severity, + Locations = + new[] { + new DiagnosticResultLocation("Test0.cs", 21, 13) + } + }; + + VerifyCSharpDiagnostic(test, expectedDiagnostics); + } + + // Tests SyntaxKind.TupleType + [TestMethod] + public void ReturnTypeAnalyzer_IncorrectReturn_ValueTuple() + { + var test = @" using System.Threading.Tasks; + using System.Collections.Generic; using Microsoft.Azure.WebJobs; + using Microsoft.Azure.WebJobs.Extensions.DurableTask; namespace VSSample { public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + [FunctionName(""ReturnTypeAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { context.SignalEntityAsync(); @@ -151,13 +236,13 @@ public static async Task> Run( public interface IEntityExample { - public static Tuple methodTestOneParameter(string test); + public static (string, int) methodTestOneParameter(string test); } }"; var expectedDiagnostics = new DiagnosticResult { Id = DiagnosticId, - Message = string.Format(Resources.EntityInterfaceReturnTypeAnalyzerMessageFormat, "Tuple"), + Message = string.Format(Resources.EntityInterfaceReturnTypeAnalyzerMessageFormat, "(string, int)"), Severity = Severity, Locations = new[] { diff --git a/test/WebJobs.Extensions.DurableTask.Analyzers.Test/EntityInterface/InterfaceAnalyzerTests.cs b/test/WebJobs.Extensions.DurableTask.Analyzers.Test/EntityInterface/InterfaceAnalyzerTests.cs index f6f438cc9..e6b188c63 100644 --- a/test/WebJobs.Extensions.DurableTask.Analyzers.Test/EntityInterface/InterfaceAnalyzerTests.cs +++ b/test/WebJobs.Extensions.DurableTask.Analyzers.Test/EntityInterface/InterfaceAnalyzerTests.cs @@ -4,7 +4,6 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.VisualStudio.TestTools.UnitTesting; -using System; using TestHelper; namespace Microsoft.Azure.WebJobs.Extensions.DurableTask.Analyzers.Test.EntityInterface @@ -13,35 +12,35 @@ namespace Microsoft.Azure.WebJobs.Extensions.DurableTask.Analyzers.Test.EntityIn public class InterfaceAnalyzerTests : CodeFixVerifier { private static readonly string DiagnosticId = InterfaceAnalyzer.DiagnosticId; - private static readonly DiagnosticSeverity Severity = InterfaceAnalyzer.Severity; - + private static readonly DiagnosticSeverity Severity = DiagnosticSeverity.Warning; + [TestMethod] - public void InterfaceAnalyzer_NonIssue() + public void InterfaceAnalyzer_NoDiagnosticTestCases() { var test = @" - using System; - using System.Collections.Generic; using System.Threading.Tasks; + using System.Collections.Generic; using Microsoft.Azure.WebJobs; + using Microsoft.Azure.WebJobs.Extensions.DurableTask; namespace VSSample { public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + [FunctionName(""InterfaceAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { context.SignalEntityAsync(); } - public static async Task> NotFunction( + public static async Task NotFunction( [OrchestrationTrigger] IDurableOrchestrationContext context) { context.SignalEntityAsync(); } - public static async Task> NotFunctionNotEntity( + public static async Task NotFunctionNotEntity( [OrchestrationTrigger] IDurableOrchestrationContext context) { context.SignalEntityAsync(); @@ -57,21 +56,22 @@ public interface IEntityExample VerifyCSharpDiagnostic(test); } + // Tests SyntaxKind.IdentifierName [TestMethod] - public void InterfaceAnalyzer_Object() + public void InterfaceAnalyzer_SignalEntityUsingObject() { var test = @" - using System; - using System.Collections.Generic; using System.Threading.Tasks; + using System.Collections.Generic; using Microsoft.Azure.WebJobs; + using Microsoft.Azure.WebJobs.Extensions.DurableTask; namespace VSSample { public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + [FunctionName(""InterfaceAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { context.SignalEntityAsync(); @@ -97,24 +97,25 @@ public interface IEntityExample VerifyCSharpDiagnostic(test, expectedDiagnostics); } + // Tests SyntaxKind.PredefinedType [TestMethod] - public void InterfaceAnalyzer_ILogger() + public void InterfaceAnalyzer_SignalEntityUsingString() { var test = @" - using System; - using System.Collections.Generic; using System.Threading.Tasks; + using System.Collections.Generic; using Microsoft.Azure.WebJobs; + using Microsoft.Azure.WebJobs.Extensions.DurableTask; namespace VSSample { public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + [FunctionName(""InterfaceAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { - context.SignalEntityAsync(); + context.SignalEntityAsync(); } } @@ -126,7 +127,7 @@ public interface IEntityExample var expectedDiagnostics = new DiagnosticResult { Id = DiagnosticId, - Message = string.Format(Resources.SignalEntityAnalyzerMessageFormat, "ILogger"), + Message = string.Format(Resources.SignalEntityAnalyzerMessageFormat, "string"), Severity = Severity, Locations = new[] { @@ -137,24 +138,66 @@ public interface IEntityExample VerifyCSharpDiagnostic(test, expectedDiagnostics); } + // Tests SyntaxKind.GenericName [TestMethod] - public void InterfaceAnalyzer_String() + public void InterfaceAnalyzer_SignalEntityUsingList() { var test = @" - using System; + using System.Threading.Tasks; using System.Collections.Generic; + using Microsoft.Azure.WebJobs; + using Microsoft.Azure.WebJobs.Extensions.DurableTask; + + namespace VSSample + { + public static class HelloSequence + { + [FunctionName(""InterfaceAnalyzerTestCases"")] + public static async Task Run( + [OrchestrationTrigger] IDurableOrchestrationContext context) + { + context.SignalEntityAsync>(); + } + } + + public interface IEntityExample + { + public static void methodTest(); + } + }"; + var expectedDiagnostics = new DiagnosticResult + { + Id = DiagnosticId, + Message = string.Format(Resources.SignalEntityAnalyzerMessageFormat, "List"), + Severity = Severity, + Locations = + new[] { + new DiagnosticResultLocation("Test0.cs", 15, 43) + } + }; + + VerifyCSharpDiagnostic(test, expectedDiagnostics); + } + + // Tests SyntaxKind.ArrayType + [TestMethod] + public void InterfaceAnalyzer_SignalEntityUsingArray() + { + var test = @" using System.Threading.Tasks; + using System.Collections.Generic; using Microsoft.Azure.WebJobs; + using Microsoft.Azure.WebJobs.Extensions.DurableTask; namespace VSSample { public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + [FunctionName(""InterfaceAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { - context.SignalEntityAsync(); + context.SignalEntityAsync(); } } @@ -166,35 +209,77 @@ public interface IEntityExample var expectedDiagnostics = new DiagnosticResult { Id = DiagnosticId, - Message = string.Format(Resources.SignalEntityAnalyzerMessageFormat, ""), + Message = string.Format(Resources.SignalEntityAnalyzerMessageFormat, "string[]"), Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 15, 42) + new DiagnosticResultLocation("Test0.cs", 15, 43) } }; VerifyCSharpDiagnostic(test, expectedDiagnostics); } + // Tests SyntaxKind.TupleType [TestMethod] - public void InterfaceAnalyzer_Tuple() + public void InterfaceAnalyzer_SignalEntityUsingValueTuple() { var test = @" - using System; + using System.Threading.Tasks; using System.Collections.Generic; + using Microsoft.Azure.WebJobs; + using Microsoft.Azure.WebJobs.Extensions.DurableTask; + + namespace VSSample + { + public static class HelloSequence + { + [FunctionName(""InterfaceAnalyzerTestCases"")] + public static async Task Run( + [OrchestrationTrigger] IDurableOrchestrationContext context) + { + context.SignalEntityAsync<(string, int)>(); + } + } + + public interface IEntityExample + { + public static void methodTest(); + } + }"; + var expectedDiagnostics = new DiagnosticResult + { + Id = DiagnosticId, + Message = string.Format(Resources.SignalEntityAnalyzerMessageFormat, "(string, int)"), + Severity = Severity, + Locations = + new[] { + new DiagnosticResultLocation("Test0.cs", 15, 43) + } + }; + + VerifyCSharpDiagnostic(test, expectedDiagnostics); + } + + // Tests interface not defined in user code + [TestMethod] + public void InterfaceAnalyzer_SignalEntityUsingILogger() + { + var test = @" using System.Threading.Tasks; + using System.Collections.Generic; using Microsoft.Azure.WebJobs; + using Microsoft.Azure.WebJobs.Extensions.DurableTask; namespace VSSample { public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + [FunctionName(""InterfaceAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { - context.SignalEntityAsync>(); + context.SignalEntityAsync(); } } @@ -206,11 +291,11 @@ public interface IEntityExample var expectedDiagnostics = new DiagnosticResult { Id = DiagnosticId, - Message = string.Format(Resources.SignalEntityAnalyzerMessageFormat, ">"), + Message = string.Format(Resources.SignalEntityAnalyzerMessageFormat, "ILogger"), Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 15, 42) + new DiagnosticResultLocation("Test0.cs", 15, 43) } }; diff --git a/test/WebJobs.Extensions.DurableTask.Analyzers.Test/EntityInterface/InterfaceContentAnalyzerTests.cs b/test/WebJobs.Extensions.DurableTask.Analyzers.Test/EntityInterface/InterfaceContentAnalyzerTests.cs index b8719bd0e..105f4909a 100644 --- a/test/WebJobs.Extensions.DurableTask.Analyzers.Test/EntityInterface/InterfaceContentAnalyzerTests.cs +++ b/test/WebJobs.Extensions.DurableTask.Analyzers.Test/EntityInterface/InterfaceContentAnalyzerTests.cs @@ -12,23 +12,23 @@ namespace Microsoft.Azure.WebJobs.Extensions.DurableTask.Analyzers.Test.EntityIn public class InterfaceContentAnalyzerTests : CodeFixVerifier { private static readonly string DiagnosticId = InterfaceContentAnalyzer.DiagnosticId; - private static readonly DiagnosticSeverity Severity = InterfaceContentAnalyzer.Severity; + private static readonly DiagnosticSeverity Severity = DiagnosticSeverity.Warning; [TestMethod] - public void InterfaceContentAnalyzer_NonIssue() + public void InterfaceContentAnalyzer_NoDiagnosticTestCases() { var test = @" - using System; - using System.Collections.Generic; using System.Threading.Tasks; + using System.Collections.Generic; using Microsoft.Azure.WebJobs; + using Microsoft.Azure.WebJobs.Extensions.DurableTask; namespace VSSample { public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + [FunctionName(""InterfaceContentAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { context.SignalEntityAsync(); @@ -44,21 +44,22 @@ public interface IEntityExample VerifyCSharpDiagnostic(test); } + // Entity interface must have at least one method. [TestMethod] public void InterfaceContentAnalyzer_NoMethods() { var test = @" - using System; - using System.Collections.Generic; using System.Threading.Tasks; + using System.Collections.Generic; using Microsoft.Azure.WebJobs; + using Microsoft.Azure.WebJobs.Extensions.DurableTask; namespace VSSample { public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + [FunctionName(""InterfaceContentAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { context.SignalEntityAsync(); @@ -83,21 +84,22 @@ public interface IEntityExample VerifyCSharpDiagnostic(test, expectedDiagnostics); } + // Entity interface must have only methods. [TestMethod] - public void InterfaceContentAnalyzer_Property() + public void InterfaceContentAnalyzer_HasProperty() { var test = @" - using System; - using System.Collections.Generic; using System.Threading.Tasks; + using System.Collections.Generic; using Microsoft.Azure.WebJobs; + using Microsoft.Azure.WebJobs.Extensions.DurableTask; namespace VSSample { public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + [FunctionName(""InterfaceContentAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { context.SignalEntityAsync(); diff --git a/test/WebJobs.Extensions.DurableTask.Analyzers.Test/EntityInterface/ParameterAnalyzerTests.cs b/test/WebJobs.Extensions.DurableTask.Analyzers.Test/EntityInterface/ParameterAnalyzerTests.cs index 09bbdde92..2dc8f91d6 100644 --- a/test/WebJobs.Extensions.DurableTask.Analyzers.Test/EntityInterface/ParameterAnalyzerTests.cs +++ b/test/WebJobs.Extensions.DurableTask.Analyzers.Test/EntityInterface/ParameterAnalyzerTests.cs @@ -12,23 +12,23 @@ namespace Microsoft.Azure.WebJobs.Extensions.DurableTask.Analyzers.Test.EntityIn public class ParameterAnalyzerTests : CodeFixVerifier { private static readonly string DiagnosticId = ParameterAnalyzer.DiagnosticId; - private static readonly DiagnosticSeverity Severity = ParameterAnalyzer.Severity; + private static readonly DiagnosticSeverity Severity = DiagnosticSeverity.Warning; [TestMethod] - public void ParameterAnalyzer_NonIssue() + public void ParameterAnalyzer_NoDiagnosticTestCases() { var test = @" - using System; - using System.Collections.Generic; using System.Threading.Tasks; + using System.Collections.Generic; using Microsoft.Azure.WebJobs; + using Microsoft.Azure.WebJobs.Extensions.DurableTask; namespace VSSample { public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + [FunctionName(""ParameterAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { context.SignalEntityAsync(); @@ -46,21 +46,22 @@ public interface IEntityExample VerifyCSharpDiagnostic(test); } + // Entity interface must have no more than 1 parameter. [TestMethod] public void ParameterAnalyzer_MoreThanOne() { var test = @" - using System; - using System.Collections.Generic; using System.Threading.Tasks; + using System.Collections.Generic; using Microsoft.Azure.WebJobs; + using Microsoft.Azure.WebJobs.Extensions.DurableTask; namespace VSSample { public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + [FunctionName(""ParameterAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { context.SignalEntityAsync(); @@ -69,13 +70,13 @@ public static async Task> Run( public interface IEntityExample { - public static void methodTestOneParameter(string test, int number); + public static void methodTestTwoParameters(string test, int number); } }"; var expectedDiagnostics = new DiagnosticResult { Id = DiagnosticId, - Message = string.Format(Resources.EntityInterfaceParameterAnalyzerMessageFormat, "public static void methodTestOneParameter(string test, int number);"), + Message = string.Format(Resources.EntityInterfaceParameterAnalyzerMessageFormat, "public static void methodTestTwoParameters(string test, int number);"), Severity = Severity, Locations = new[] { diff --git a/test/WebJobs.Extensions.DurableTask.Analyzers.Test/Orchestrator/DateTimeAnalyzerTests.cs b/test/WebJobs.Extensions.DurableTask.Analyzers.Test/Orchestrator/DateTimeAnalyzerTests.cs index e0592625c..96a2bb2ec 100644 --- a/test/WebJobs.Extensions.DurableTask.Analyzers.Test/Orchestrator/DateTimeAnalyzerTests.cs +++ b/test/WebJobs.Extensions.DurableTask.Analyzers.Test/Orchestrator/DateTimeAnalyzerTests.cs @@ -14,10 +14,10 @@ namespace Microsoft.Azure.WebJobs.Extensions.DurableTask.Analyzers.Test.Orchestr public class DateTimeAnalyzerTests : CodeFixVerifier { private static readonly string DiagnosticId = DateTimeAnalyzer.DiagnosticId; - private static readonly DiagnosticSeverity Severity = DateTimeAnalyzer.Severity; + private static readonly DiagnosticSeverity Severity = DiagnosticSeverity.Warning; - private const string allTests = @" - public void dateTimeNow() + private const string allNonDiagnosticTestCases = @" + public async Task dateTimeNow() { System.DateTime.Now; System.DateTime.UtcNow; @@ -31,57 +31,60 @@ public void dateTimeNow() private const string ExpectedFix = @" using System; - using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Azure.WebJobs; + using Microsoft.Azure.WebJobs.Extensions.DurableTask; namespace VSSample { public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + [FunctionName(""DateTimeAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { - context.CurrentUtcDateTime; + var dateTime = context.CurrentUtcDateTime; } } }"; [TestMethod] - public void DateTime_InMethod_NonIssueCalls() + public void DateTime_NotOrchestrator_NoDiagnosticTestCases() { var test = @" using System; + using System.Threading.Tasks; + using Microsoft.Azure.WebJobs; + using Microsoft.Azure.WebJobs.Extensions.DurableTask; namespace VSSample { - public static class DateTimeNowExample + public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - " + allTests; + [FunctionName(""DateTimeAnalyzerTestCases"")] + " + allNonDiagnosticTestCases; VerifyCSharpDiagnostic(test); } [TestMethod] - public void DateTimeInOrchestrator_Now_Namespace() + public void DateTime_Now_WithNamespace() { var test = @" using System; - using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Azure.WebJobs; + using Microsoft.Azure.WebJobs.Extensions.DurableTask; namespace VSSample { public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + [FunctionName(""DateTimeAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { - System.DateTime.Now; + var dateTime = System.DateTime.Now; } } }"; @@ -92,7 +95,7 @@ public static async Task> Run( Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 15, 17) + new DiagnosticResultLocation("Test0.cs", 15, 32) } }; @@ -102,23 +105,23 @@ public static async Task> Run( } [TestMethod] - public void DateTimeInOrchestrator_UtcNow_Namespace() + public void DateTime_UtcNow_WithNamespace() { var test = @" using System; - using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Azure.WebJobs; + using Microsoft.Azure.WebJobs.Extensions.DurableTask; namespace VSSample { public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + [FunctionName(""DateTimeAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { - System.DateTime.UtcNow; + var dateTime = System.DateTime.UtcNow; } } }"; @@ -129,7 +132,7 @@ public static async Task> Run( Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 15, 17) + new DiagnosticResultLocation("Test0.cs", 15, 32) } }; @@ -139,23 +142,23 @@ public static async Task> Run( } [TestMethod] - public void DateTimeInOrchestrator_Today_Namespace() + public void DateTime_Today_WithNamespace() { var test = @" using System; - using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Azure.WebJobs; + using Microsoft.Azure.WebJobs.Extensions.DurableTask; namespace VSSample { public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + [FunctionName(""DateTimeAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { - System.DateTime.Today; + var dateTime = System.DateTime.Today; } } }"; @@ -166,7 +169,7 @@ public static async Task> Run( Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 15, 17) + new DiagnosticResultLocation("Test0.cs", 15, 32) } }; @@ -176,23 +179,23 @@ public static async Task> Run( } [TestMethod] - public void DateTimeInOrchestrator_Now() + public void DateTime_Now() { var test = @" using System; - using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Azure.WebJobs; + using Microsoft.Azure.WebJobs.Extensions.DurableTask; namespace VSSample { public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + [FunctionName(""DateTimeAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { - DateTime.Now; + var dateTime = DateTime.Now; } } }"; @@ -203,7 +206,7 @@ public static async Task> Run( Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 15, 17) + new DiagnosticResultLocation("Test0.cs", 15, 32) } }; @@ -213,23 +216,23 @@ public static async Task> Run( } [TestMethod] - public void DateTimeInOrchestrator_UtcNow() + public void DateTime_UtcNow() { var test = @" using System; - using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Azure.WebJobs; + using Microsoft.Azure.WebJobs.Extensions.DurableTask; namespace VSSample { public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + [FunctionName(""DateTimeAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { - DateTime.UtcNow; + var dateTime = DateTime.UtcNow; } } }"; @@ -240,7 +243,7 @@ public static async Task> Run( Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 15, 17) + new DiagnosticResultLocation("Test0.cs", 15, 32) } }; @@ -250,23 +253,23 @@ public static async Task> Run( } [TestMethod] - public void DateTimeInOrchestrator_Today() + public void DateTime_Today() { var test = @" using System; - using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Azure.WebJobs; + using Microsoft.Azure.WebJobs.Extensions.DurableTask; namespace VSSample { public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + [FunctionName(""DateTimeAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { - DateTime.Today; + var dateTime = DateTime.Today; } } }"; @@ -277,7 +280,7 @@ public static async Task> Run( Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 15, 17) + new DiagnosticResultLocation("Test0.cs", 15, 32) } }; @@ -287,32 +290,28 @@ public static async Task> Run( } [TestMethod] - public void DateTime_InMethod_OrchestratorCall_All() + public void DateTime_NonDeterministicMethod_AllDateTimeCases() { var test = @" -using System; -using System.Collections.Generic; -using System.Net; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Azure.WebJobs.Extensions.DurableTask; - -namespace VSSample -{ - public static class HelloSequence + using System; + using System.Threading.Tasks; + using Microsoft.Azure.WebJobs; + using Microsoft.Azure.WebJobs.Extensions.DurableTask; + + namespace VSSample { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + public static class HelloSequence + { + [FunctionName(""DateTimeAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { DirectCall(); - - return ""Hello""; } - public static string DirectCall() - { - " + allTests; + public static string DirectCall() + { + " + allNonDiagnosticTestCases; var expectedDiagnostics = new DiagnosticResult[7]; @@ -323,7 +322,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 17, 17) + new DiagnosticResultLocation("Test0.cs", 15, 17) } }; @@ -334,7 +333,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 27, 13) + new DiagnosticResultLocation("Test0.cs", 23, 13) } }; @@ -345,7 +344,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 28, 13) + new DiagnosticResultLocation("Test0.cs", 24, 13) } }; @@ -356,7 +355,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 29, 13) + new DiagnosticResultLocation("Test0.cs", 25, 13) } }; @@ -367,7 +366,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 30, 4) + new DiagnosticResultLocation("Test0.cs", 26, 4) } }; @@ -378,7 +377,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 31, 4) + new DiagnosticResultLocation("Test0.cs", 27, 4) } }; @@ -389,7 +388,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 32, 13) + new DiagnosticResultLocation("Test0.cs", 28, 13) } }; diff --git a/test/WebJobs.Extensions.DurableTask.Analyzers.Test/Orchestrator/EnvironmentVariableAnalyzerTests.cs b/test/WebJobs.Extensions.DurableTask.Analyzers.Test/Orchestrator/EnvironmentVariableAnalyzerTests.cs index 563bc372c..134c8e735 100644 --- a/test/WebJobs.Extensions.DurableTask.Analyzers.Test/Orchestrator/EnvironmentVariableAnalyzerTests.cs +++ b/test/WebJobs.Extensions.DurableTask.Analyzers.Test/Orchestrator/EnvironmentVariableAnalyzerTests.cs @@ -12,7 +12,7 @@ namespace Microsoft.Azure.WebJobs.Extensions.DurableTask.Analyzers.Test.Orchestr public class EnvironmentVariableAnalyzerTests : CodeFixVerifier { private static readonly string DiagnosticId = EnvironmentVariableAnalyzer.DiagnosticId; - private static readonly DiagnosticSeverity Severity = EnvironmentVariableAnalyzer.Severity; + private static readonly DiagnosticSeverity Severity = DiagnosticSeverity.Warning; private const string allTests = @" public void environmentAllCalls() @@ -28,36 +28,39 @@ public void environmentAllCalls() }"; [TestMethod] - public void EnvironmentVariables_NonIssueCalls() + public void Environment_NoDiagnosticTestCases() { var test = @" using System; + using System.Threading.Tasks; + using Microsoft.Azure.WebJobs; + using Microsoft.Azure.WebJobs.Extensions.DurableTask; namespace VSSample { - public static class EnvironmentExample + public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] + [FunctionName(""EnvironmentVariableAnalyzerTestCases"")] " + allTests; VerifyCSharpDiagnostic(test); } [TestMethod] - public void GetEnvironmentVariable_InOrchestrator() + public void Environment_GetEnvironmentVariable() { var test = @" using System; - using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Azure.WebJobs; + using Microsoft.Azure.WebJobs.Extensions.DurableTask; namespace VSSample { public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + [FunctionName(""EnvironmentVariableAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { Environment.GetEnvironmentVariable(""test""); @@ -79,20 +82,20 @@ public static async Task> Run( } [TestMethod] - public void GetEnvironmentVariable_InOrchestrator_Namespace() + public void Environment_GetEnvironmentVariable_WithNamespace() { var test = @" using System; - using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Azure.WebJobs; + using Microsoft.Azure.WebJobs.Extensions.DurableTask; namespace VSSample { public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + [FunctionName(""EnvironmentVariableAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { System.Environment.GetEnvironmentVariable(""test""); @@ -114,20 +117,20 @@ public static async Task> Run( } [TestMethod] - public void GetEnvironmentVariables_InOrchestrator() + public void Environment_GetEnvironmentVariables() { var test = @" using System; - using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Azure.WebJobs; + using Microsoft.Azure.WebJobs.Extensions.DurableTask; namespace VSSample { public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + [FunctionName(""EnvironmentVariableAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { Environment.GetEnvironmentVariables(); @@ -149,20 +152,20 @@ public static async Task> Run( } [TestMethod] - public void GetEnvironmentVariables_InOrchestrator_Namespace() + public void Environment_GetEnvironmentVariables_WithNamespace() { var test = @" using System; - using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Azure.WebJobs; + using Microsoft.Azure.WebJobs.Extensions.DurableTask; namespace VSSample { public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + [FunctionName(""EnvironmentVariableAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { System.Environment.GetEnvironmentVariables(); @@ -184,20 +187,20 @@ public static async Task> Run( } [TestMethod] - public void ExpandEnvironmentVariables_InOrchestrator() + public void Enviornment_ExpandEnvironmentVariables() { var test = @" using System; - using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Azure.WebJobs; + using Microsoft.Azure.WebJobs.Extensions.DurableTask; namespace VSSample { public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + [FunctionName(""EnvironmentVariableAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { Environment.ExpandEnvironmentVariables(""test""); @@ -219,20 +222,20 @@ public static async Task> Run( } [TestMethod] - public void ExpandEnvironmentVariables_InOrchestrator_Namespace() + public void Environment_ExpandEnvironmentVariables_WithNamespace() { var test = @" using System; - using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Azure.WebJobs; + using Microsoft.Azure.WebJobs.Extensions.DurableTask; namespace VSSample { public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + [FunctionName(""EnvironmentVariableAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { System.Environment.ExpandEnvironmentVariables(""test""); @@ -254,27 +257,23 @@ public static async Task> Run( } [TestMethod] - public void GetEnvironmentVariables_InMethod_OrchestratorCall_All() + public void Enviornment_NonDeterministicMethod_AllEnvironmentCases() { var test = @" -using System; -using System.Collections.Generic; -using System.Net; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Azure.WebJobs.Extensions.DurableTask; - -namespace VSSample -{ - public static class HelloSequence + using System; + using System.Threading.Tasks; + using Microsoft.Azure.WebJobs; + using Microsoft.Azure.WebJobs.Extensions.DurableTask; + + namespace VSSample { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + public static class HelloSequence + { + [FunctionName(""EnvironmentVariableAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { DirectCall(); - - return ""Hello""; } public static string DirectCall() @@ -290,7 +289,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 17, 17) + new DiagnosticResultLocation("Test0.cs", 15, 17) } }; @@ -301,7 +300,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 27, 13) + new DiagnosticResultLocation("Test0.cs", 23, 13) } }; @@ -312,7 +311,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 28, 13) + new DiagnosticResultLocation("Test0.cs", 24, 13) } }; @@ -323,7 +322,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 29, 13) + new DiagnosticResultLocation("Test0.cs", 25, 13) } }; @@ -334,7 +333,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 30, 13) + new DiagnosticResultLocation("Test0.cs", 26, 13) } }; @@ -345,7 +344,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 31, 13) + new DiagnosticResultLocation("Test0.cs", 27, 13) } }; @@ -356,7 +355,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 32, 13) + new DiagnosticResultLocation("Test0.cs", 28, 13) } }; diff --git a/test/WebJobs.Extensions.DurableTask.Analyzers.Test/Orchestrator/GuidAnalyzerTests.cs b/test/WebJobs.Extensions.DurableTask.Analyzers.Test/Orchestrator/GuidAnalyzerTests.cs index 3c2a6753d..830117100 100644 --- a/test/WebJobs.Extensions.DurableTask.Analyzers.Test/Orchestrator/GuidAnalyzerTests.cs +++ b/test/WebJobs.Extensions.DurableTask.Analyzers.Test/Orchestrator/GuidAnalyzerTests.cs @@ -13,7 +13,7 @@ namespace Microsoft.Azure.WebJobs.Extensions.DurableTask.Analyzers.Test.Orchestr public class GuidAnalyzerTests : CodeFixVerifier { private static readonly string DiagnosticId = GuidAnalyzer.DiagnosticId; - private static readonly DiagnosticSeverity Severity = GuidAnalyzer.Severity; + private static readonly DiagnosticSeverity Severity = DiagnosticSeverity.Warning; private const string allTests = @" public void guidAllCalls() @@ -26,6 +26,7 @@ public void guidAllCalls() private const string ExpectedFix = @" using System; + using System.Threading.Tasks; using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.DurableTask; @@ -33,8 +34,8 @@ namespace VSSample { public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - public static void Run( + [FunctionName(""GuidAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { context.NewGuid(); @@ -43,26 +44,30 @@ public static void Run( }"; [TestMethod] - public void NewGuid_NonIssueCalls() + public void Guid_NoDiagnosticTestCases() { var test = @" using System; + using System.Threading.Tasks; + using Microsoft.Azure.WebJobs; + using Microsoft.Azure.WebJobs.Extensions.DurableTask; namespace VSSample { - public static class GuidNewGuidExample + public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] + [FunctionName(""GuidAnalyzerTestCases"")] " + allTests; VerifyCSharpDiagnostic(test); } [TestMethod] - public void NewGuidInOrchestrator() + public void Guid_NewGuid() { var test = @" using System; + using System.Threading.Tasks; using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.DurableTask; @@ -70,8 +75,8 @@ namespace VSSample { public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - public static void Run( + [FunctionName(""GuidAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { Guid.NewGuid(); @@ -85,7 +90,7 @@ public static void Run( Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 14, 17) + new DiagnosticResultLocation("Test0.cs", 15, 17) } }; @@ -95,10 +100,11 @@ public static void Run( } [TestMethod] - public void NewGuidInOrchestrator_Namespace() + public void Guid_NewGuid_WithNamespace() { var test = @" using System; + using System.Threading.Tasks; using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.DurableTask; @@ -106,8 +112,8 @@ namespace VSSample { public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - public static void Run( + [FunctionName(""GuidAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { System.Guid.NewGuid(); @@ -121,7 +127,7 @@ public static void Run( Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 14, 17) + new DiagnosticResultLocation("Test0.cs", 15, 17) } }; @@ -131,24 +137,23 @@ public static void Run( } [TestMethod] - public void NewGuid_InMethod_OrchestratorCall_All() + public void Guid_NonDeterministicMethod_AllGuidCases() { var test = @" using System; + using System.Threading.Tasks; using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.DurableTask; namespace VSSample -{ - public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + public static class HelloSequence + { + [FunctionName(""GuidAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { DirectCall(); - - return ""Hello""; } public static string DirectCall() @@ -164,7 +169,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 14, 17) + new DiagnosticResultLocation("Test0.cs", 15, 17) } }; @@ -175,7 +180,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 24, 13) + new DiagnosticResultLocation("Test0.cs", 23, 13) } }; @@ -186,7 +191,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 25, 13) + new DiagnosticResultLocation("Test0.cs", 24, 13) } }; diff --git a/test/WebJobs.Extensions.DurableTask.Analyzers.Test/Orchestrator/IOTypesAnalyzerTests.cs b/test/WebJobs.Extensions.DurableTask.Analyzers.Test/Orchestrator/IOTypesAnalyzerTests.cs index 7e8a167a9..73f83707c 100644 --- a/test/WebJobs.Extensions.DurableTask.Analyzers.Test/Orchestrator/IOTypesAnalyzerTests.cs +++ b/test/WebJobs.Extensions.DurableTask.Analyzers.Test/Orchestrator/IOTypesAnalyzerTests.cs @@ -12,7 +12,7 @@ namespace Microsoft.Azure.WebJobs.Extensions.DurableTask.Analyzers.Test.Orchestr public class IOTypesAnalyzerTests : CodeFixVerifier { private static readonly string DiagnosticId = IOTypesAnalyzer.DiagnosticId; - private static readonly DiagnosticSeverity Severity = IOTypesAnalyzer.Severity; + private static readonly DiagnosticSeverity Severity = DiagnosticSeverity.Warning; private const string allTests = @" public void allCalls(HttpClient httpClient, SqlConnection sqlConnection, CloudBlobClient cloudBlobClient, CloudQueueClient cloudQueueClient, @@ -40,7 +40,7 @@ public void allCalls(HttpClient httpClient, SqlConnection sqlConnection, CloudBl }"; [TestMethod] - public void IOTypes_NonIssueCalls() + public void IOTypes_NoDiagnosticTestCases() { var test = @" using System; @@ -57,14 +57,14 @@ namespace VSSample public static class NonIssueExample { - [FunctionName(""E1_HelloSequence"")] + [FunctionName(""IOTypesAnalyzerTestCases"")] " + allTests; VerifyCSharpDiagnostic(test); } [TestMethod] - public void HttpClientInOrchestrator_Class() + public void HttpClient_DiagnosticOnClass() { var test = @" using System; @@ -76,8 +76,8 @@ namespace VSSample { public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + [FunctionName(""IOTypesAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { HttpClient httpClient = new HttpClient(); @@ -99,7 +99,7 @@ public static async Task> Run( } [TestMethod] - public void HttpClientInOrchestrator_Varaible() + public void HttpClient_DiagnosticOnVaraible() { var test = @" using System; @@ -111,8 +111,8 @@ namespace VSSample { public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + [FunctionName(""IOTypesAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context, HttpClient httpClient) { httpClient.GetType(); @@ -146,7 +146,7 @@ public static async Task> Run( } [TestMethod] - public void SqlConectionInOrchestrator_Class() + public void SqlConection_DiagnosticOnClass() { var test = @" using System; @@ -158,8 +158,8 @@ namespace VSSample { public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + [FunctionName(""IOTypesAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { SqlConnection sqlConnection; @@ -181,7 +181,7 @@ public static async Task> Run( } [TestMethod] - public void SqlConnectionInOrchestrator_Varaible() + public void SqlConnection_DiagnosticOnVaraible() { var test = @" using System; @@ -193,8 +193,8 @@ namespace VSSample { public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + [FunctionName(""IOTypesAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context, SqlConnection sqlConnection) { sqlConnection.GetType(); @@ -228,7 +228,7 @@ public static async Task> Run( } [TestMethod] - public void CloudBlobClientInOrchestrator_Class() + public void CloudBlobClient_DiagnosticOnClass() { var test = @" using System; @@ -240,8 +240,8 @@ namespace VSSample { public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + [FunctionName(""IOTypesAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { CloudBlobClient cloudBlobClient; @@ -263,7 +263,7 @@ public static async Task> Run( } [TestMethod] - public void CloudBlobClientInOrchestrator_Varaible() + public void CloudBlobClient_DiagnosticOnVaraible() { var test = @" using System; @@ -275,8 +275,8 @@ namespace VSSample { public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + [FunctionName(""IOTypesAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context, CloudBlobClient cloudBlobClient) { cloudBlobClient.GetType(); @@ -310,7 +310,7 @@ public static async Task> Run( } [TestMethod] - public void CloudQueueClientInOrchestrator_Class() + public void CloudQueueClient_DiagnosticOnClass() { var test = @" using System; @@ -322,8 +322,8 @@ namespace VSSample { public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + [FunctionName(""IOTypesAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { CloudQueueClient cloudQueueClient; @@ -345,7 +345,7 @@ public static async Task> Run( } [TestMethod] - public void CloudQueueClientInOrchestrator_Varaible() + public void CloudQueueClient_DiagnosticOnVaraible() { var test = @" using System; @@ -357,8 +357,8 @@ namespace VSSample { public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + [FunctionName(""IOTypesAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context, CloudQueueClient cloudQueueClient) { cloudQueueClient.GetType(); @@ -392,7 +392,7 @@ public static async Task> Run( } [TestMethod] - public void CloudTableClientInOrchestrator_Class() + public void CloudTableClient_DiagnosticOnClass() { var test = @" using System; @@ -404,8 +404,8 @@ namespace VSSample { public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + [FunctionName(""IOTypesAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { CloudTableClient cloudTableClient; @@ -427,7 +427,7 @@ public static async Task> Run( } [TestMethod] - public void CloudTableClientInOrchestrator_Varaible() + public void CloudTableClient_DiagnosticOnVaraible() { var test = @" using System; @@ -439,8 +439,8 @@ namespace VSSample { public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + [FunctionName(""IOTypesAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context, CloudTableClient cloudTableClient) { cloudTableClient.GetType(); @@ -474,7 +474,7 @@ public static async Task> Run( } [TestMethod] - public void DocumentClientInOrchestrator_Class() + public void DocumentClient_DiagnosticOnClass() { var test = @" using System; @@ -486,8 +486,8 @@ namespace VSSample { public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + [FunctionName(""IOTypesAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { DocumentClient documentClient; @@ -509,7 +509,7 @@ public static async Task> Run( } [TestMethod] - public void DocumentClienttInOrchestrator_Varaible() + public void DocumentClient_DiagnosticOnVaraible() { var test = @" using System; @@ -521,8 +521,8 @@ namespace VSSample { public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + [FunctionName(""IOTypesAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context, DocumentClient documentClient) { documentClient.Dispose(); @@ -556,7 +556,7 @@ public static async Task> Run( } [TestMethod] - public void WebRequestInOrchestrator_Class() + public void WebRequest_DiagnosticOnClass() { var test = @" using System; @@ -568,8 +568,8 @@ namespace VSSample { public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + [FunctionName(""IOTypesAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { WebRequest webRequest; @@ -591,7 +591,7 @@ public static async Task> Run( } [TestMethod] - public void WebRequesttInOrchestrator_Varaible() + public void WebRequest_DiagnosticOnVaraible() { var test = @" using System; @@ -603,8 +603,8 @@ namespace VSSample { public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + [FunctionName(""IOTypesAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context, WebRequest webRequest) { webRequest.GetType(); @@ -638,7 +638,7 @@ public static async Task> Run( } [TestMethod] - public void IOTypes_InMethod_OrchestratorCall_All() + public void IOTypes_NonDeterministicMethod_AllIOTypesCases() { var test = @" using System; @@ -652,13 +652,11 @@ namespace VSSample { public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + [FunctionName(""IOTypesAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { DirectCall(); - - return ""Hello""; } public static string DirectCall() @@ -685,7 +683,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 25, 34) + new DiagnosticResultLocation("Test0.cs", 23, 34) } }; @@ -696,7 +694,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 25, 57) + new DiagnosticResultLocation("Test0.cs", 23, 57) } }; @@ -707,7 +705,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 25, 86) + new DiagnosticResultLocation("Test0.cs", 23, 86) } }; @@ -718,7 +716,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 25, 119) + new DiagnosticResultLocation("Test0.cs", 23, 119) } }; @@ -729,7 +727,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 26, 17) + new DiagnosticResultLocation("Test0.cs", 24, 17) } }; @@ -740,7 +738,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 26, 52) + new DiagnosticResultLocation("Test0.cs", 24, 52) } }; @@ -751,7 +749,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 26, 83) + new DiagnosticResultLocation("Test0.cs", 24, 83) } }; @@ -762,7 +760,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 30, 17) + new DiagnosticResultLocation("Test0.cs", 28, 17) } }; @@ -773,7 +771,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 31, 17) + new DiagnosticResultLocation("Test0.cs", 29, 17) } }; @@ -784,7 +782,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 32, 17) + new DiagnosticResultLocation("Test0.cs", 30, 17) } }; @@ -795,7 +793,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 33, 17) + new DiagnosticResultLocation("Test0.cs", 31, 17) } }; @@ -806,7 +804,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 34, 17) + new DiagnosticResultLocation("Test0.cs", 32, 17) } }; @@ -817,7 +815,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 35, 17) + new DiagnosticResultLocation("Test0.cs", 33, 17) } }; @@ -828,7 +826,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 36, 17) + new DiagnosticResultLocation("Test0.cs", 34, 17) } }; @@ -839,7 +837,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 38, 17) + new DiagnosticResultLocation("Test0.cs", 36, 17) } }; @@ -850,7 +848,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 39, 17) + new DiagnosticResultLocation("Test0.cs", 37, 17) } }; @@ -861,7 +859,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 40, 17) + new DiagnosticResultLocation("Test0.cs", 38, 17) } }; @@ -872,7 +870,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 41, 17) + new DiagnosticResultLocation("Test0.cs", 39, 17) } }; @@ -883,7 +881,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 42, 17) + new DiagnosticResultLocation("Test0.cs", 40, 17) } }; @@ -894,7 +892,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 43, 17) + new DiagnosticResultLocation("Test0.cs", 41, 17) } }; @@ -905,7 +903,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 44, 17) + new DiagnosticResultLocation("Test0.cs", 42, 17) } }; diff --git a/test/WebJobs.Extensions.DurableTask.Analyzers.Test/Orchestrator/MethodAnalyzerTests.cs b/test/WebJobs.Extensions.DurableTask.Analyzers.Test/Orchestrator/MethodAnalyzerTests.cs index 3520318d3..c68c07184 100644 --- a/test/WebJobs.Extensions.DurableTask.Analyzers.Test/Orchestrator/MethodAnalyzerTests.cs +++ b/test/WebJobs.Extensions.DurableTask.Analyzers.Test/Orchestrator/MethodAnalyzerTests.cs @@ -4,9 +4,6 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.VisualStudio.TestTools.UnitTesting; -using System; -using System.Collections.Generic; -using System.Text; using TestHelper; namespace Microsoft.Azure.WebJobs.Extensions.DurableTask.Analyzers.Test.Orchestrator @@ -15,50 +12,43 @@ namespace Microsoft.Azure.WebJobs.Extensions.DurableTask.Analyzers.Test.Orchestr public class MethodAnalyzerTests : CodeFixVerifier { private static readonly string DiagnosticId = MethodAnalyzer.DiagnosticId; - private static readonly DiagnosticSeverity Severity = MethodAnalyzer.Severity; + private static readonly DiagnosticSeverity Severity = DiagnosticSeverity.Warning; [TestMethod] - public void MethodCallsInOrchestrator_NonIssueCalls() + public void MethodCalls_NoDiagnosticTestCases() { var test = @" -using System; -using System.Collections.Generic; -using System.Net; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Azure.WebJobs.Extensions.DurableTask; + using System; + using System.Threading.Tasks; + using Microsoft.Azure.WebJobs; + using Microsoft.Azure.WebJobs.Extensions.DurableTask; -namespace VSSample -{ - public static class HelloSequence + namespace VSSample { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + public static class HelloSequence + { + [FunctionName(""MethodAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { string.ToLower(""Testing method call not defined in source code""); DirectCall(); - - return ""Hello""; } - public static string DirectCall() + public static void DirectCall() { string.ToUpper(""Method not defined in source code""); IndirectCall(); RecursiveCall(); - return ""Hi""; } - public static Object IndirectCall() + public static void IndirectCall() { - return new Object(); } - public static Object RecursiveCall() + public static void RecursiveCall() { RecursiveCall(); - return new Object(); } } }"; @@ -67,33 +57,28 @@ public static Object RecursiveCall() } [TestMethod] - public void MethodCallsInOrchestrator_DirectCall() + public void MethodCalls_DirectCall() { var test = @" -using System; -using System.Collections.Generic; -using System.Net; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Azure.WebJobs.Extensions.DurableTask; + using System; + using System.Threading.Tasks; + using Microsoft.Azure.WebJobs; + using Microsoft.Azure.WebJobs.Extensions.DurableTask; -namespace VSSample -{ - public static class HelloSequence + namespace VSSample { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + public static class HelloSequence + { + [FunctionName(""MethodAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { DirectCall(); - - return ""Hello""; } - public static string DirectCall() + public static void DirectCall() { var dateTime = DateTime.Now; - return ""Hi""; } } }"; @@ -106,7 +91,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 17, 17) + new DiagnosticResultLocation("Test0.cs", 15, 17) } }; @@ -117,7 +102,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 24, 28) + new DiagnosticResultLocation("Test0.cs", 20, 28) } }; @@ -125,39 +110,33 @@ public static string DirectCall() } [TestMethod] - public void MethodCallsInOrchestrator_IndirectCall() + public void MethodCalls_IndirectCall() { var test = @" -using System; -using System.Collections.Generic; -using System.Net; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Azure.WebJobs.Extensions.DurableTask; + using System; + using System.Threading.Tasks; + using Microsoft.Azure.WebJobs; + using Microsoft.Azure.WebJobs.Extensions.DurableTask; -namespace VSSample -{ - public static class HelloSequence + namespace VSSample { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + public static class HelloSequence + { + [FunctionName(""MethodAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { DirectCall(); - - return ""Hello""; } - public static string DirectCall() + public static void DirectCall() { IndirectCall(); - return ""Hi""; } - public static Object IndirectCall() + public static void IndirectCall() { var dateTime = DateTime.Now; - return new Object(); } } }"; @@ -170,7 +149,7 @@ public static Object IndirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 17, 17) + new DiagnosticResultLocation("Test0.cs", 15, 17) } }; @@ -181,7 +160,7 @@ public static Object IndirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 24, 13) + new DiagnosticResultLocation("Test0.cs", 20, 13) } }; @@ -192,7 +171,7 @@ public static Object IndirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 30, 28) + new DiagnosticResultLocation("Test0.cs", 25, 28) } }; @@ -200,34 +179,29 @@ public static Object IndirectCall() } [TestMethod] - public void MethodCallsInOrchestrator_NonShortCircuit() + public void MethodCalls_NonShortCircuit() { var test = @" -using System; -using System.Collections.Generic; -using System.Net; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Azure.WebJobs.Extensions.DurableTask; + using System; + using System.Threading.Tasks; + using Microsoft.Azure.WebJobs; + using Microsoft.Azure.WebJobs.Extensions.DurableTask; -namespace VSSample -{ - public static class HelloSequence + namespace VSSample { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + public static class HelloSequence + { + [FunctionName(""MethodAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { DirectCall(); - - return ""Hello""; } - public static string DirectCall() + public static void DirectCall() { var dateTime = DateTime.Now; Environment.GetEnvironmentVariable(""test""); - return ""Hi""; } } }"; @@ -240,7 +214,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 17, 17) + new DiagnosticResultLocation("Test0.cs", 15, 17) } }; @@ -251,7 +225,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 24, 28) + new DiagnosticResultLocation("Test0.cs", 20, 28) } }; @@ -262,7 +236,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 25, 13) + new DiagnosticResultLocation("Test0.cs", 21, 13) } }; diff --git a/test/WebJobs.Extensions.DurableTask.Analyzers.Test/Orchestrator/ThreadTaskAnalyzerTests.cs b/test/WebJobs.Extensions.DurableTask.Analyzers.Test/Orchestrator/ThreadTaskAnalyzerTests.cs index af098b577..32af120a9 100644 --- a/test/WebJobs.Extensions.DurableTask.Analyzers.Test/Orchestrator/ThreadTaskAnalyzerTests.cs +++ b/test/WebJobs.Extensions.DurableTask.Analyzers.Test/Orchestrator/ThreadTaskAnalyzerTests.cs @@ -12,7 +12,7 @@ namespace Microsoft.Azure.WebJobs.Extensions.DurableTask.Analyzers.Test.Orchestr public class ThreadTaskAnalyzerTests : CodeFixVerifier { private static readonly string DiagnosticId = ThreadTaskAnalyzer.DiagnosticId; - private static readonly DiagnosticSeverity Severity = ThreadTaskAnalyzer.Severity; + private static readonly DiagnosticSeverity Severity = DiagnosticSeverity.Warning; private const string allTests = @" public void threadTaskAllCalls() @@ -38,36 +38,40 @@ public void threadTaskAllCalls() }"; [TestMethod] - public void ThreadTaskInMethod_NonIssueCalls() + public void ThreadTask_NoDiagnosticTestCases() { var test = @" - using System; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.Azure.WebJobs; + using Microsoft.Azure.WebJobs.Extensions.DurableTask; namespace VSSample { - public static class ThreadTaskNonIssueCalls + public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] + [FunctionName(""hreadTaskAnalyzerTestCases"")] + public static async Task Run( " + allTests; VerifyCSharpDiagnostic(test); } [TestMethod] - public void TaskInOrchestrator_Run_Namespace() + public void Task_Run_WithNamespace() { var test = @" - using System; - using System.Collections.Generic; + using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.WebJobs; + using Microsoft.Azure.WebJobs.Extensions.DurableTask; namespace VSSample { public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + [FunctionName(""hreadTaskAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { System.Threading.Tasks.Task.Run(() => { }); @@ -89,20 +93,20 @@ public static async Task> Run( } [TestMethod] - public void TaskInOrchestrator_Run() + public void Task_Run() { var test = @" - using System; - using System.Collections.Generic; + using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.WebJobs; + using Microsoft.Azure.WebJobs.Extensions.DurableTask; namespace VSSample { public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + [FunctionName(""hreadTaskAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { Task.Run(() => { }); @@ -124,20 +128,20 @@ public static async Task> Run( } [TestMethod] - public void TaskInOrchestrator_Factory_StartNew_Namespace() + public void Task_Factory_StartNew_WithNamespace() { var test = @" - using System; - using System.Collections.Generic; + using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.WebJobs; + using Microsoft.Azure.WebJobs.Extensions.DurableTask; namespace VSSample { public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + [FunctionName(""hreadTaskAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { System.Threading.Tasks.Task.Factory.StartNew(() => { }); @@ -159,20 +163,20 @@ public static async Task> Run( } [TestMethod] - public void TaskInOrchestrator_Factory_StartNew() + public void Task_Factory_StartNew() { var test = @" - using System; - using System.Collections.Generic; + using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.WebJobs; + using Microsoft.Azure.WebJobs.Extensions.DurableTask; namespace VSSample { public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + [FunctionName(""hreadTaskAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { Task.Factory.StartNew(() => { }); @@ -194,20 +198,20 @@ public static async Task> Run( } [TestMethod] - public void ThreadInOrchestrator_Start_Namespace() + public void Thread_Start_WithNamespace() { var test = @" - using System; - using System.Collections.Generic; using System.Threading; + using System.Threading.Tasks; using Microsoft.Azure.WebJobs; + using Microsoft.Azure.WebJobs.Extensions.DurableTask; namespace VSSample { public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + [FunctionName(""hreadTaskAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { var t = new System.Threading.Thread(() => { }); @@ -230,20 +234,20 @@ public static async Task> Run( } [TestMethod] - public void ThreadInOrchestrator_Start() + public void Thread_Start() { var test = @" - using System; - using System.Collections.Generic; using System.Threading; + using System.Threading.Tasks; using Microsoft.Azure.WebJobs; + using Microsoft.Azure.WebJobs.Extensions.DurableTask; namespace VSSample { public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + [FunctionName(""hreadTaskAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { var t = new Thread(() => { }); @@ -266,21 +270,20 @@ public static async Task> Run( } [TestMethod] - public void TaskInOrchestrator_ContinueWith_Namespace() + public void Task_ContinueWith_WithNamespace() { var test = @" - using System; - using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.WebJobs; + using Microsoft.Azure.WebJobs.Extensions.DurableTask; namespace VSSample { public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + [FunctionName(""hreadTaskAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { System.Threading.Tasks.Task task = new System.Threading.Tasks.Task(); @@ -295,7 +298,7 @@ public static async Task> Run( Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 17, 17) + new DiagnosticResultLocation("Test0.cs", 16, 17) } }; @@ -303,21 +306,20 @@ public static async Task> Run( } [TestMethod] - public void TaskInOrchestrator_ContinueWith() + public void Task_ContinueWith() { var test = @" - using System; - using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.WebJobs; + using Microsoft.Azure.WebJobs.Extensions.DurableTask; namespace VSSample { public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + [FunctionName(""hreadTaskAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { Task task = new Task(); @@ -332,7 +334,7 @@ public static async Task> Run( Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 17, 17) + new DiagnosticResultLocation("Test0.cs", 16, 17) } }; @@ -340,21 +342,20 @@ public static async Task> Run( } [TestMethod] - public void TaskInOrchestrator_ContinueWith_ExecuteSynchronously() + public void Task_ContinueWith_ExecuteSynchronously() { var test = @" - using System; - using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.WebJobs; + using Microsoft.Azure.WebJobs.Extensions.DurableTask; namespace VSSample { public static class HelloSequence { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + [FunctionName(""hreadTaskAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { Task task = new Task(); @@ -367,27 +368,23 @@ public static async Task> Run( } [TestMethod] - public void ThreadTask_InMethod_OrchestratorCall_All() + public void ThreadTask_NonDeterministicMethod_AllThreadTaskCases() { var test = @" -using System; -using System.Collections.Generic; -using System.Net; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Azure.WebJobs.Extensions.DurableTask; - -namespace VSSample -{ - public static class HelloSequence + using System.Threading; + using System.Threading.Tasks; + using Microsoft.Azure.WebJobs; + using Microsoft.Azure.WebJobs.Extensions.DurableTask; + + namespace VSSample { - [FunctionName(""E1_HelloSequence"")] - public static async Task> Run( + public static class HelloSequence + { + [FunctionName(""hreadTaskAnalyzerTestCases"")] + public static async Task Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { DirectCall(); - - return ""Hello""; } public static string DirectCall() @@ -403,7 +400,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 17, 17) + new DiagnosticResultLocation("Test0.cs", 15, 17) } }; @@ -414,7 +411,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 27, 23) + new DiagnosticResultLocation("Test0.cs", 23, 23) } }; @@ -425,7 +422,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 28, 23) + new DiagnosticResultLocation("Test0.cs", 24, 23) } }; @@ -436,7 +433,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 29, 23) + new DiagnosticResultLocation("Test0.cs", 25, 23) } }; @@ -447,7 +444,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 30, 23) + new DiagnosticResultLocation("Test0.cs", 26, 23) } }; @@ -458,7 +455,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 33, 17) + new DiagnosticResultLocation("Test0.cs", 29, 17) } }; @@ -469,7 +466,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 35, 17) + new DiagnosticResultLocation("Test0.cs", 31, 17) } }; @@ -480,7 +477,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 38, 17) + new DiagnosticResultLocation("Test0.cs", 34, 17) } }; @@ -491,7 +488,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 40, 17) + new DiagnosticResultLocation("Test0.cs", 36, 17) } }; diff --git a/test/WebJobs.Extensions.DurableTask.Analyzers.Test/Orchestrator/TimerAnalyzerTests.cs b/test/WebJobs.Extensions.DurableTask.Analyzers.Test/Orchestrator/TimerAnalyzerTests.cs index 013091407..fe6c8aa12 100644 --- a/test/WebJobs.Extensions.DurableTask.Analyzers.Test/Orchestrator/TimerAnalyzerTests.cs +++ b/test/WebJobs.Extensions.DurableTask.Analyzers.Test/Orchestrator/TimerAnalyzerTests.cs @@ -13,7 +13,7 @@ namespace Microsoft.Azure.WebJobs.Extensions.DurableTask.Analyzers.Test.Orchestr public class TimerAnalyzerTests : CodeFixVerifier { private static readonly string DiagnosticId = TimerAnalyzer.DiagnosticId; - private static readonly DiagnosticSeverity Severity = TimerAnalyzer.Severity; + private static readonly DiagnosticSeverity Severity = DiagnosticSeverity.Warning; private const string allTests = @" public void timerAllCalls() @@ -47,7 +47,7 @@ public static async Task> Run( }"; [TestMethod] - public void TimerInMethod_NonIssueCalls() + public void Timer_NoDiagnosticTestCases() { var test = @" using System; @@ -63,7 +63,7 @@ public static class TimerNonIssueCalls } [TestMethod] - public void TaskInOrchestrator_Delay_Namespace() + public void Task_Delay_WithNamespace() { var test = @" using System; @@ -101,7 +101,7 @@ public static async Task> Run( } [TestMethod] - public void TaskInOrchestrator_Delay() + public void Task_Delay() { var test = @" using System; @@ -139,7 +139,7 @@ public static async Task> Run( } [TestMethod] - public void ThreadInOrchestrator_Sleep_Namespace() + public void Thread_Sleep_WithNamespace() { var test = @" using System; @@ -177,7 +177,7 @@ public static async Task> Run( } [TestMethod] - public void ThreadInOrchestrator_Sleep() + public void Thread_Sleep() { var test = @" using System; @@ -215,7 +215,7 @@ public static async Task> Run( } [TestMethod] - public void Timer_InMethod_OrchestratorCall_All() + public void Timer_NonDeterministicMethod_AllTimerCases() { var test = @" using System; @@ -234,8 +234,6 @@ public static async Task> Run( [OrchestrationTrigger] IDurableOrchestrationContext context) { DirectCall(); - - return ""Hello""; } public static string DirectCall() @@ -262,7 +260,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 27, 17) + new DiagnosticResultLocation("Test0.cs", 25, 17) } }; @@ -273,7 +271,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 28, 17) + new DiagnosticResultLocation("Test0.cs", 26, 17) } }; @@ -284,7 +282,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 29, 17) + new DiagnosticResultLocation("Test0.cs", 27, 17) } }; @@ -295,7 +293,7 @@ public static string DirectCall() Severity = Severity, Locations = new[] { - new DiagnosticResultLocation("Test0.cs", 30, 17) + new DiagnosticResultLocation("Test0.cs", 28, 17) } };