The function node_save() catches exceptions now in Drupal 7.
http://api.drupal.org/api/function/node_save/7

This means that in DrupalWebTestCase::drupalCreateNode(), it doesn't necessarily notice when a node hasn't actually been created. It still returns a valid-looking node object, even though the node hasn't been created. That seems like a really bad idea to me in a test, and makes debugging the test difficult, because it should have failed at drupalCreateNode, but instead you don't see failures until later on when you try to do something with the node.

I would propose doing a node_load() in drupalCreateNode after node_save, and returning the result of the node_load. And also throwing an error if the node_load() fails. This would allow drupalCreateNode() to catch problems in creation of nodes.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

jhodgdon’s picture

Actually, shouldn't node_save() do some kind of drupal_set_message to tell you that node creation failed in the UI?

jhodgdon’s picture

Title: DrupalWebTestCase::drupalCreateNode doesn't report failure » Node creation failures are not handled well
Component: simpletest.module » node.module
FileSize
3.72 KB

I'm changing this to being a bug in the node module. This is totally wrong...

I tested this in the UI, by enabling the test module that I am trying to debug, which is currently preventing the creation of all node content due to some bug I haven't found yet.

I went to the Create Content page, and typed in some random text for title (I typed in "asdf") and body, and clicked "save".

Here's what I see: a "new asdf has been created", and a 404 error (normally it would take me to the page I just created, but it wasn't created due to my module having a problem).

This seems totally wrong...

jhodgdon’s picture

FileSize
6.56 KB

Here's a screen shot of the dblog for this.

There's an error from node (that's the error that caused the node not to be saved).
Then there's a notice from "content" saying that "asdf" has beed added, which is not actually true.
Then there's the page not found error for node/2, correctly stating that the page cannot be found.

Anyway, it seems to me that there's a problem if node_save doesn't report to its calling function or to the UI that an error occurred. And DrupalWebTestCase::drupalCreateNode() also needs to be aware that node_save didn't work, for testing purposes.

moshe weitzman’s picture

API functions like node_save() don't set messages or issue watchdog and so on. thats the responsibility of the caller.

jhodgdon’s picture

Sure. The caller (in this case node.module) doesn't do the right thing then.

But node_save() also doesn't really give any indication to the caller about whether it succeeded or not.

catch’s picture

It could do $status = drupal_write_record($node); then return $status at the end, that gives you 1 or 2 depending on whether it's an insert or update and would be consistent with taxonomy_term_save() and possibly others. The exception watchdog message ought to be caught by test bot but I think there's an outstanding patch for that.

jhodgdon’s picture

There are three problems here, as I see it:

a) node_save() doesn't report failure/exception except to dblog. Should probably have a return value indicating success/failure in some way, and if possible, probably returning the error message.

b) In the regular Drupal UI, if there's a failure when saving a node, the UI doesn't tell you anything about it (see screen shot in #2 above).

c) In DrupalWebTestCase::drupalCreateNode, it needs to report a test failure if the node is not created.

If (a) is fixed, it will facilitate fixing (b) and (c).

(b) is critical for UI at release, IMO.

(c) is critical for testing, IMO.

catch’s picture

Agreed this is critical, just eating errors and writing them to dblog is a recipe for hard to find bugs all over the place, and a serious regression from D6.

cha0s’s picture

IMO, the exception should be propagated up, that is if node_save wants to catch it and write to watchdog then fine, but I think it should re-throw the exception is has caught. I am currently unaware of the scope of changes this would require in calling code.

willmoy’s picture

NODE_SAVE() RETURN VALUE

As catch said, node_save() should return FALSE if any of its drupal_write_record() calls return FALSE.
Same applies to _node_save_revision() and therefore _node_save_revision needs to return FALSE on failure, based on its drupal_write_record() return.

I'm not qualified to judge whether re-throwing the exception makes sense but it would allow the UI to give more useful error messages.

UI

The good news is that only 5 functions call node_save:
- book_admin_edit_submit in modules/book/book.admin.inc
- node_form_submit in modules/node/node.pages.inc
- node_revision_revert_confirm_submit in modules/node/node.pages.inc
- node_save_action in modules/node/node.module
- _node_mass_update_helper in modules/node/node.admin.inc
http://api.drupal.org/api/function/node_save/7 (I love that site)

So fixing the UI actually reporting failure shouldn't be too hard.

TESTS

Testing does handle exceptions, apparently:
#305077: Rework simpletest backend solved #293521: Simpletest should catch exceptions and #242069: Simpletest should include exception data in reporting so this should follow naturally. Also #304924: Extend drupal_error_handler to manage exceptions

willmoy’s picture

Status: Active » Needs review
FileSize
9.75 KB

This patch doesn't fiddle with exceptions because it doesn't seem necessary for the actual calling functions. Provides a return status for node_save() and _node_save_revision() and implements what seem to me to be reasonable responses for the callers.

UI:
- book_admin_edit_submit in modules/book/book.admin.inc - report failure to change title but carry on with ordering and other nodes
- node_form_submit in modules/node/node.pages.inc - report failure
- node_revision_revert_confirm_submit in modules/node/node.pages.inc - report failure
- node_save_action in modules/node/node.module - return status, which actions_do passes to its calling function. These don't appear to take any notice, but that's not our problem here.
- _node_mass_update_helper in modules/node/node.admin.inc - made it take a node rather than a nid, and return the status. Calling functions then drupal_set_message() if there's an error. This implies a lot of drupal_set_messages() but is very unlikely to happen, I'd have thought.

moshe weitzman’s picture

+++ modules/book/book.admin.inc	21 Feb 2010 15:55:07 -0000
@@ -131,7 +131,10 @@ function book_admin_edit_submit($form, &
-        node_save($node);
+        $status = node_save($node);
+        if (!$status) {
+          drupal_set_message(t('Could not update title for %title.', array('%title' => $node->title)), 'error');
+        }
         watchdog('content', 'book: updated %title.', array('%title' => $node->title), WATCHDOG_NOTICE, l(t('view'), 'node/' . $node->nid));

the watchdog() should be in an else(), no?

Otherwise, looks good.

Powered by Dreditor.

willmoy’s picture

cha0s’s picture

FileSize
9.92 KB

Rerolled because '*=' is the wrong operator to use with boolean logic. Use '&=' instead.

Also, I must admit I think this is much worse than throwing relevant exceptions at failure points, instead of just aggregating a boolean state by accumulating failures over the course of the procedure. At very least we should be dropping out early on the first fail. It's crazy that the function is failing on the first $return = FALSE, but yet doing like 85 other things between there and the return.

Putting in my vote for throwing exceptions, but on the chance that no one really listens or cares, here's the rerolled patch to use the proper operators for this... approach.

Status: Needs review » Needs work

The last submitted patch, 704646.patch, failed testing.

cha0s’s picture

Status: Needs work » Needs review

Oh, I see why. Can't do &&=, silly me. Maybe *= is better here? Seems wrong to use arithmetic to get boolean answers. I appreciate 'clever hax' as much as the next guy but it at least warrants a comment if we keep this, yeah?

willmoy’s picture

$foo = TRUE;
$foo &= FALSE;
$foo &= TRUE;
var_dump($foo);

$foo = TRUE;
$foo *= FALSE;
$foo *= TRUE;
var_dump($foo);

Prints int(0) int(0). No difference, but no boolean either. No idea, sorry.

cha0s’s picture

I think I know why... it's because the rvalue in those expr's don't necessarily equal 1.

1 & 2 = 0 :)

jhodgdon’s picture

Then how about just
$foo = $foo && TRUE;

That is guaranteed to work. No need to use a *= or &= shorthand that is not guaranteed to work?

jhodgdon’s picture

Status: Needs review » Needs work

Other comments on the patches above in #13 and #14:

a) Bad indentation and extra spaces on that second line after the = sign:

+      $node = node_load($nid, NULL, TRUE);
+     $status =  _node_mass_update_helper($node, $updates);
+      if (!$status) {

b) Function header definitely needs doc:
/**
* Node Mass Update - helper function.
*/
-function _node_mass_update_helper($nid, $updates) {
- $node = node_load($nid, NULL, TRUE);
+function _node_mass_update_helper(&$node, $updates) {

c) Given this new function def, shouldn't this function be passing in $node not $nid? How could this have been working at all? There must be a missing test...

+    $node = node_load($nid, NULL, TRUE);
+    $status = _node_mass_update_helper($nid, $updates);

d) Needs a "." after failure:

+ *
+ * @return
+ *   TRUE on success, FALSE on failure

e) I definitely agree that as soon as any kind of failure is detected, node_save() should abort and return. And the
$return &= or $return *= lines should just go away. But if you do go that route, it should be $return = $return &&.

f) Shouldn't catching an exception also cause $return to be FALSE?

   catch (Exception $e) {
     $transaction->rollback('node', $e->getMessage(), array(), WATCHDOG_ERROR);
   }
+  return $return;

g) We are not using the words "post" and "node" in Drupal 6 UI. Please call it "content item".

+    drupal_set_message(t('The post could not be saved.'), 'error');
cha0s’s picture

Status: Needs work » Needs review
FileSize
10.26 KB

Yo, got a new patch here.

Addresses the things jhodgdon mentioned, and also makes more sense, logically.

Instead of just plowing over stuff when there's a fail (current version) or just tiptoeing over sensitive stuff once there's a fail ($return = $return &&), I've implemented exceptions for each of the failure points.

Now, if anything fails, an exception is thrown, which jumps right over anything else and right to the transaction rollback (Something that wasn't handled on non-exception-throwing failures), and allow us to mark the return as FALSE from the catch block, instead of having it all over the place.

jhodgdon’s picture

Status: Needs review » Needs work
Issue tags: +Needs tests

The code is MUCH better! A couple of things:

a) This is not going to work:

     $nid = array_shift($context['sandbox']['nodes']);
-    $node = _node_mass_update_helper($nid, $updates);
+    $node = node_load($nid, NULL, TRUE);
+    $status = _node_mass_update_helper($nid, $updates);

The _node_mass_update_helper function takes $node not $nid as its first parameter.

b) Because of (a) and the tests passing, there must not be a test that exercises _node_mass_update_batch_process. There should be.

c) If you're fixing things anyway:

    catch (Exception $e) {
     $transaction->rollback('node', $e->getMessage(), array(), WATCHDOG_ERROR);
+
+    $return = FALSE;
   }
+  return $return;

Why not just return FALSE inside the catch, and return TRUE at the end? There is really no need for the $return variable in your new code.

cha0s’s picture

Status: Needs work » Needs review
FileSize
12.39 KB

Good call on the return variable. I also fixed the batch update function, and added a test.

Oh, and I also added a check for node_save in the SimpleTest system.

Let's see what zee bot thinks.

Status: Needs review » Needs work

The last submitted patch, 704646.patch, failed testing.

cha0s’s picture

Status: Needs work » Needs review
FileSize
12.39 KB

Ah, I invoked the batch function incorrectly from the test.

jhodgdon’s picture

Status: Needs review » Needs work

This patch is closer...

- I would like to see a test added that makes node_save() fail and verifies that the error message is shown. It could use a test class that somehow makes node creation fail in one of the Node API hooks (for example, try to update a database table that doesn't exist or do a query with a syntax error in it), and verify that the correct error message is generated from the path node/add/[type] when it can't create the node.
- I think the test for _node_mass_update_batch_process() should be calling the public API function node_mass_update() instead of calling the internal helper function directly.
- I also think the message "'Node could not be created for drupalCreateNode()." in drupalCreateNode should probably just say "Node could not be created". None of the other messages in DrupalWebTestCase are formatted like that, and the test results page does tell you the function name and line number anyway.
- Also, drupalCreateNode() should not return a node object if it fails, which is what it was doing before, and this patch doesn't change that.

casey’s picture

Subscribing.

Berdir’s picture

+++ modules/node/node.admin.inc	23 Feb 2010 18:47:31 -0000
@@ -300,22 +300,23 @@ function node_mass_update($nodes, $updat
+      $node = node_load($nid, NULL, TRUE);
+      if (!_node_mass_update_helper($node, $updates)) {
+        drupal_set_message(t('An error occurred while processing %title. The %type could not be saved.', array('%title' => $node->title, '%type' => $node->type)), 'error');
+      }

The %type.. sentence doesn't make much to me. Do we even need that one?

+++ modules/node/node.admin.inc	23 Feb 2010 18:47:31 -0000
@@ -300,22 +300,23 @@ function node_mass_update($nodes, $updat
+function _node_mass_update_helper(&$node, $updates) {
   foreach ($updates as $name => $value) {
     $node->$name = $value;
   }
-  node_save($node);
-  return $node;
+  return node_save($node);
 }

I see why it is necessary, but this seems like an API change. Yes, it's probably necessary and it's an internal helper function, Just saying.. :)

+++ modules/node/node.admin.inc	23 Feb 2010 18:47:31 -0000
@@ -333,7 +334,11 @@ function _node_mass_update_batch_process
+    $node = node_load($nid, NULL, TRUE);
+    $status = _node_mass_update_helper($node, $updates);
+    if (!$status) {
+      drupal_set_message(t('An error occurred while processing %title. The %type could not be saved.', array('%title' => $node->title, '%type' => $node->type)), 'error');
+    }

Same as above

+++ modules/node/node.module	23 Feb 2010 18:47:34 -0000
@@ -1013,22 +1016,32 @@ function node_save($node) {
+      if (SAVED_NEW != drupal_write_record('node', $node)) {
+        throw new Exception('saving node');
+      }
+      if (!_node_save_revision($node, $user->uid)) {
+        throw new Exception('saving node revision');
+      }

Imho, Exception shouldn't be used directly but something like NodeSaveException instead. Also, the strings can be improved. It looks strange that they aren't translated but that's because they are passed to watchdog and translated when displayed. Quite a mess imho, but nothing we can do about it...

+++ modules/simpletest/drupal_web_test_case.php	23 Feb 2010 18:47:37 -0000
@@ -777,7 +777,9 @@ class DrupalWebTestCase extends DrupalTe
-    node_save($node);
+    if (!node_save($node)) {
+      $this->fail(t('Node could not be created for drupalCreateNode().'), t('SimpleTest'));
+    }

Agreed that we dont the function name in the message and can't we just use $this->assertTrue(node_save($node), .. here?

Powered by Dreditor.

jhodgdon’s picture

Regarding Berdir's first comment, I actually think the error message is pretty good. It would read something like:
"An error occurred while processing My Node Title. The Page cannot be saved."
I think we do need to tell them that it wasn't saved, and this seems like a clear way to do it.

Berdir’s picture

Hm, maybe :) But note that $node->type is the machine readable name....

jhodgdon’s picture

Good point! The message should be changed to retrieve the human-readable name of the type, just as the message would if the save had been successful.

cha0s’s picture

Just wanna bump this to the top of my queue.

catch’s picture

Per #9 we should just re-throw the exception, bloating the calling code like this just in case there's a critical error seems unreasonable to me. If there's an exception you want a big red error, not a nice message saying sorry.

jpmckinney’s picture

Status: Needs work » Needs review
FileSize
335 bytes

I agree. We should not be using exceptions half-way. Exceptions were introduced to programming specifically to avoid handling and throwing return values of FALSE and TRUE all the way up the call chain. Here is a patch that re-throws exceptions, addressing the issues in the OP and in #7.

jpmckinney’s picture

Title: node_save() fails silently » Node creation failures are not handled well
FileSize
335 bytes
jpmckinney’s picture

Title: Node creation failures are not handled well » node_save() fails silently

Changing title to not presume handling exceptions is the right choice.

Status: Needs review » Needs work

The last submitted patch, 704646-34.patch, failed testing.

jpmckinney’s picture

Status: Needs work » Needs review
FileSize
1.5 KB

Update test to expect exception.

Status: Needs review » Needs work

The last submitted patch, 704646-36.patch, failed testing.

jpmckinney’s picture

FileSize
2.36 KB

I think I need to be calling node_save() instead of drupalPost in this test, as otherwise I can't catch the exception thrown by node_test_exception_node_insert.

jpmckinney’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 704646-40.patch, failed testing.

mradcliffe’s picture

Status: Needs work » Needs review
FileSize
2.67 KB

re-rolled as a cvs diff patch.

catch’s picture

Title: Node creation failures are not handled well » node_save() fails silently
Status: Needs review » Reviewed & tested by the community

I didn't even know hook_node_post_save() existed seems quite useless given _insert() and fapi both exist, we should get rid of that in D8. Patch looks good, tests pass.

jhodgdon’s picture

hook_node_post_save doesn't exist. That's a - line in the patch.

ADDED: See http://api.drupal.org/api/group/node_api_hooks/7 for a list of the actual hooks (hopefully it's still up to date). Whether they're all needed or not is open to question...

catch’s picture

Oh I see, just the existing comment is wrong talking about a hook which doesn't exist, should've realised since there's no change to the dummy module itself. Good cleanup either way.

Dries’s picture

Status: Reviewed & tested by the community » Fixed

Committed to CVS HEAD -- glad to see it was synced with comment_save() fails silently.

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.

glacialheart’s picture

Was this ever fixed for D6? I'm having this exact issue with node_save() on a D6 site. I checked the {watchdog} table and found no errors. I am at a loss to identify what the error is. Each time I call node_save() the nid gets incremented yet no new node is added to the table.

Berdir’s picture

This doesn't affect Drupal 6. The problem here was that the exceptions was thrown and there was no way to catch it. There are no exceptions used in D6. Make sure that you have error messages enabled.

glacialheart’s picture

I have messages enabled. No messages are displayed. I get a message when I create a node/add/story but nothing when I use node_save(). (I don't want to hijack this thread, should I start a new one? But, I'm not sure that I have a real issue)

Ciprian Oancia’s picture

Hi everybody

in my case i need for several content types that the "the content x has been created" doesn't display other i will have a button for the next step. Is there any way i can disable this confirmation?

thank you