Skip to content

Commit 2cbf6d6

Browse files
committed
Add unit tests for script definition and references
Signed-off-by: Ben Sherman <[email protected]>
1 parent f031ba9 commit 2cbf6d6

File tree

4 files changed

+315
-61
lines changed

4 files changed

+315
-61
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Copyright 2024-2025, Seqera Labs
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package nextflow.lsp
18+
19+
import java.nio.file.Files
20+
import java.nio.file.Path
21+
22+
import nextflow.lsp.services.LanguageServerConfiguration
23+
import nextflow.lsp.services.LanguageService
24+
import nextflow.lsp.services.script.ScriptService
25+
import org.eclipse.lsp4j.DidOpenTextDocumentParams
26+
import org.eclipse.lsp4j.TextDocumentItem
27+
28+
/**
29+
*
30+
* @author Ben Sherman <[email protected]>
31+
*/
32+
class TestUtils {
33+
34+
private static final Path workspaceRoot = workspaceRoot()
35+
36+
private static Path workspaceRoot() {
37+
def workspaceRoot = Path.of(System.getProperty('user.dir')).resolve('build/test_workspace/')
38+
if( !Files.exists(workspaceRoot) )
39+
workspaceRoot.toFile().mkdirs()
40+
return workspaceRoot
41+
}
42+
43+
/**
44+
* Approximate the language service waiting for a debounced update.
45+
*/
46+
static void awaitUpdate() {
47+
sleep(1200)
48+
}
49+
50+
/**
51+
* Get a language service instance for Nextflow scripts.
52+
*/
53+
static ScriptService getScriptService() {
54+
def service = new ScriptService()
55+
def configuration = LanguageServerConfiguration.defaults()
56+
service.connect(new TestLanguageClient())
57+
service.initialize(workspaceRoot.toUri().toString(), configuration)
58+
return service
59+
}
60+
61+
/**
62+
* Get the URI for a relative path.
63+
*
64+
* @param path
65+
*/
66+
static String getUri(String path) {
67+
return workspaceRoot.resolve(path).toUri().toString()
68+
}
69+
70+
/**
71+
* Open a file.
72+
*
73+
* NOTE: this operation is asynchronous due to debouncing
74+
*
75+
* @param service
76+
* @param uri
77+
* @param contents
78+
*/
79+
static void open(LanguageService service, String uri, String contents) {
80+
def textDocumentItem = new TextDocumentItem(uri, 'nextflow', 1, contents.stripIndent())
81+
service.didOpen(new DidOpenTextDocumentParams(textDocumentItem))
82+
}
83+
84+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
* Copyright 2024-2025, Seqera Labs
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package nextflow.lsp.services.script
18+
19+
import org.eclipse.lsp4j.DefinitionParams
20+
import org.eclipse.lsp4j.Location
21+
import org.eclipse.lsp4j.Position
22+
import org.eclipse.lsp4j.TextDocumentIdentifier
23+
import spock.lang.Specification
24+
25+
import static nextflow.lsp.TestUtils.*
26+
27+
/**
28+
*
29+
* @author Ben Sherman <[email protected]>
30+
*/
31+
class ScriptDefinitionTest extends Specification {
32+
33+
Location getDefinition(ScriptService service, String uri, Position position) {
34+
def locations = service
35+
.definition(new DefinitionParams(new TextDocumentIdentifier(uri), position))
36+
.getLeft()
37+
return locations.size() > 0 ? locations.first() : null
38+
}
39+
40+
def 'should get the definition of a workflow in the same file' () {
41+
given:
42+
def service = getScriptService()
43+
def uri = getUri('main.nf')
44+
45+
when:
46+
def contents = '''\
47+
workflow HELLO {
48+
}
49+
50+
workflow {
51+
HELLO()
52+
}
53+
'''
54+
open(service, uri, contents)
55+
awaitUpdate()
56+
def location = getDefinition(service, uri, new Position(4, 4))
57+
then:
58+
location != null
59+
location.getUri() == uri
60+
location.getRange().getStart() == new Position(0, 0)
61+
location.getRange().getEnd() == new Position(1, 1)
62+
}
63+
64+
def 'should get the definition of a workflow in a different file' () {
65+
given:
66+
def service = getScriptService()
67+
def mainUri = getUri('main.nf')
68+
def moduleUri = getUri('module.nf')
69+
70+
when:
71+
open(service, moduleUri, '''\
72+
workflow HELLO {
73+
}
74+
''')
75+
open(service, mainUri, '''\
76+
include { HELLO } from './module.nf'
77+
78+
workflow {
79+
HELLO()
80+
}
81+
''')
82+
awaitUpdate()
83+
def location = getDefinition(service, mainUri, new Position(3, 4))
84+
then:
85+
location != null
86+
location.getUri() == moduleUri
87+
location.getRange().getStart() == new Position(0, 0)
88+
location.getRange().getEnd() == new Position(1, 1)
89+
}
90+
91+
}

src/test/groovy/nextflow/lsp/services/script/ScriptFormattingTest.groovy

+36-61
Original file line numberDiff line numberDiff line change
@@ -16,105 +16,80 @@
1616

1717
package nextflow.lsp.services.script
1818

19-
import java.nio.file.Files
20-
import java.nio.file.Path
21-
22-
import nextflow.lsp.TestLanguageClient
23-
import nextflow.lsp.services.LanguageServerConfiguration
2419
import nextflow.script.formatter.FormattingOptions
25-
import org.eclipse.lsp4j.DidOpenTextDocumentParams
26-
import org.eclipse.lsp4j.Position
27-
import org.eclipse.lsp4j.TextDocumentItem
2820
import spock.lang.Specification
2921

22+
import static nextflow.lsp.TestUtils.*
23+
3024
/**
3125
*
3226
* @author Ben Sherman <[email protected]>
3327
*/
3428
class ScriptFormattingTest extends Specification {
3529

36-
Path getWorkspaceRoot() {
37-
def workspaceRoot = Path.of(System.getProperty('user.dir')).resolve('build/test_workspace/')
38-
if( !Files.exists(workspaceRoot) )
39-
workspaceRoot.toFile().mkdirs()
40-
return workspaceRoot
41-
}
42-
43-
ScriptService getService(Path workspaceRoot) {
44-
def service = new ScriptService()
45-
def configuration = LanguageServerConfiguration.defaults()
46-
service.connect(new TestLanguageClient())
47-
service.initialize(workspaceRoot.toUri().toString(), configuration)
48-
return service
49-
}
50-
51-
String openAndFormat(ScriptService service, Path filePath, String contents) {
52-
def uri = filePath.toUri()
53-
def textDocumentItem = new TextDocumentItem(uri.toString(), 'nextflow', 1, contents)
54-
service.didOpen(new DidOpenTextDocumentParams(textDocumentItem))
55-
def textEdits = service.formatting(uri, new FormattingOptions(4, true))
56-
return textEdits.first().getNewText()
30+
boolean checkFormat(ScriptService service, String uri, String before, String after) {
31+
open(service, uri, before)
32+
def textEdits = service.formatting(URI.create(uri), new FormattingOptions(4, true))
33+
return textEdits.first().getNewText() == after.stripIndent()
5734
}
5835

5936
def 'should format a script' () {
6037
given:
61-
def workspaceRoot = getWorkspaceRoot()
62-
def service = getService(workspaceRoot)
63-
def filePath = workspaceRoot.resolve('main.nf')
38+
def service = getScriptService()
39+
def uri = getUri('main.nf')
6440

65-
when:
66-
def contents = '''\
41+
expect:
42+
checkFormat(service, uri,
43+
'''\
6744
workflow { println 'Hello!' }
68-
'''.stripIndent()
69-
then:
70-
openAndFormat(service, filePath, contents) == '''\
45+
''',
46+
'''\
7147
workflow {
7248
println('Hello!')
7349
}
74-
'''.stripIndent()
75-
76-
when:
77-
contents = '''\
50+
'''
51+
)
52+
checkFormat(service, uri,
53+
'''\
7854
workflow {
7955
println('Hello!')
8056
}
81-
'''.stripIndent()
82-
then:
83-
openAndFormat(service, filePath, contents) == '''\
57+
''',
58+
'''\
8459
workflow {
8560
println('Hello!')
8661
}
87-
'''.stripIndent()
62+
'''
63+
)
8864
}
8965

9066
def 'should format an include declaration' () {
9167
given:
92-
def workspaceRoot = getWorkspaceRoot()
93-
def service = getService(workspaceRoot)
94-
def filePath = workspaceRoot.resolve('main.nf')
68+
def service = getScriptService()
69+
def uri = getUri('main.nf')
9570

96-
when:
97-
def contents = '''\
71+
expect:
72+
checkFormat(service, uri,
73+
'''\
9874
include{foo;bar}from'./foobar.nf'
99-
'''.stripIndent()
100-
then:
101-
openAndFormat(service, filePath, contents) == '''\
75+
''',
76+
'''\
10277
include { foo ; bar } from './foobar.nf'
103-
'''.stripIndent()
104-
105-
when:
106-
contents = '''\
78+
'''
79+
)
80+
checkFormat(service, uri,
81+
'''\
10782
include{
10883
foo;bar
10984
}from'./foobar.nf'
110-
'''.stripIndent()
111-
then:
112-
openAndFormat(service, filePath, contents) == '''\
85+
''',
86+
'''\
11387
include {
11488
foo ;
11589
bar
11690
} from './foobar.nf'
117-
'''.stripIndent()
91+
'''
92+
)
11893
}
11994

12095
}

0 commit comments

Comments
 (0)